diff options
428 files changed, 60063 insertions, 20554 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..cadee1cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +*.tar.gz +*.pc +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +compile +config.guess +config.h +config.h.in +config.log +config.rpath +config.status +config.sub +configure +depcomp +install-sh +libltdl +libtool +ltmain.sh +missing +stamp-* diff --git a/Makefile.am b/Makefile.am index 1b77187d..61396575 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,3 @@ -# $Id$ -# # This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify @@ -18,7 +16,7 @@ # USA. EXTRA_DIST = bootstrap.sh LICENSE GPL LGPL doxygen/Makefile.am doxygen/Makefile.in doxygen/doxygen.conf.in README todo -SUBDIRS=libltdl src doxygen +SUBDIRS=src doxygen man MAINTAINERCLEANFILES = noinst_DATA = @@ -45,4 +43,20 @@ homepage: all dist 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..4439c713 --- /dev/null +++ b/PROTOCOL @@ -0,0 +1,136 @@ +### 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 + +### v13, implemented by >= 0.9.11 + +New fields for PA_COMMAND_CREATE_PLAYBACK_STREAM, PA_COMMAND_CREATE_RECORD_STREAM request at the end: + + peak_detect (bool) + adjust_latency (bool) + +Replace field "name" for PA_COMMAND_CREATE_PLAYBACK_STREAM, PA_COMMAND_CREATE_RECORD_STREAM at the end: + + proplist + +Replace field "name" for PA_COMMAND_SET_CLIENT_NAME request at the end: + + proplist + +On response of PA_COMMAND_SET_CLIENT_NAME: + + client_index + +New proplist field for sink, source, sink input, source output introspection opcodes and at the end: + + proplist + +New opcodes for proplist modifications + + PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST + PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST + PA_COMMAND_UPDATE_CLIENT_PROPLIST + PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST + PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST + PA_COMMAND_REMOVE_CLIENT_PROPLIST + +New field for PA_COMMAND_PLAY_SAMPLE: + + proplist + +New field for PA_COMMAND_PLAY_SAMPLE response: + + idx + +New field for PA_COMMAND_CREATE_PLAYBACK_STREAM at the end: + + start_muted + +Buffer attributes for PA_COMMAND_CREATE_PLAYBACK_STREAM and +PA_COMMAND_CREATE_RECORD_STREAM may now be 0 for default values. + +New filed for PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR, +PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR at the end: + + adjust_latency (bool) + +new message: + + PA_COMMAND_STARTED @@ -1 +1,34 @@ -For more information see http://pulseaudio.org/ +PULSEAUDIO SOUND SERVER + +WEB SITE: + http://pulseaudio.org/ + +GIT: + git://git.0pointer.de/pulseaudio.git + +GITWEB: + http://git.0pointer.de/?p=pulseaudio.git;a=summary + +MAILING LIST: + https://tango.0pointer.de/mailman/listinfo/pulseaudio-discuss + +GIT COMMITS MAILING LIST: + https://tango.0pointer.de/mailman/listinfo/pulseaudio-commits + +TRAC TICKET CHANGES MAILING LIST: + https://tango.0pointer.de/mailman/listinfo/pulseaudio-tickets + +IRC: + #pulseaudio on irc.freenode.org + +CIA: + http://cia.navi.cx/stats/project/polypaudio + +FRESHMEAT: + http://freshmeat.net/projects/pulseaudio/ + +OHLOH: + http://www.ohloh.net/projects/4038 + +AUTHORS: + Several diff --git a/acinclude.m4 b/acinclude.m4 index 723eb87e..02c05186 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -45,7 +45,7 @@ dnl @category InstalledPackages dnl @author Steven G. Johnson <stevenj@alum.mit.edu> dnl @version 2006-05-29 dnl @license GPLWithACException -dnl +dnl dnl Checks for GCC shared/pthread inconsistency based on work by dnl Marcin Owsiany <marcin@owsiany.pl> @@ -230,7 +230,7 @@ if test "x$acx_pthread_ok" = xyes; then # 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 @@ -270,13 +270,13 @@ if test "x$acx_pthread_ok" = xyes; then 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 @@ -289,7 +289,7 @@ if test "x$acx_pthread_ok" = xyes; then 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" @@ -308,7 +308,7 @@ if test "x$acx_pthread_ok" = xyes; then 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" @@ -371,7 +371,7 @@ AC_LANG_PROGRAM( #include <syslog.h> int allow_severity = LOG_INFO; int deny_severity = LOG_WARNING;], -[struct request_info *req; +[struct request_info *req; return hosts_access (req);]), [AC_DEFINE(HAVE_LIBWRAP, [], [Have tcpwrap?]) LIBWRAP_LIBS="-lwrap" @@ -1,5 +1,4 @@ #!/bin/sh -# $Id$ # This file is part of PulseAudio. # diff --git a/bootstrap.sh b/bootstrap.sh index b85f025e..ceea55b7 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,5 +1,4 @@ #!/bin/bash -# $Id$ # This file is part of PulseAudio. # @@ -17,7 +16,7 @@ # 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 +VERSION=1.10 run_versioned() { local P @@ -25,10 +24,10 @@ run_versioned() { V=$(echo "$2" | sed -e 's,\.,,g') - if [ -e "`which $1$V`" ] ; then + if [ -e "`which $1$V 2> /dev/null`" ] ; then P="$1$V" else - if [ -e "`which $1-$2`" ] ; then + if [ -e "`which $1-$2 2> /dev/null`" ] ; then P="$1-$2" else P="$1" @@ -48,13 +47,14 @@ 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" -a -c --foreign + 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 "$@" diff --git a/configure.ac b/configure.ac index 2f09279c..296dc17e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,10 +1,11 @@ # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. -# $Id$ - # This file is part of PulseAudio. # +# Copyright 2004-2008 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 @@ -19,28 +20,45 @@ # 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) +AC_PREREQ(2.60) m4_define(PA_MAJOR, [0]) m4_define(PA_MINOR, [9]) -m4_define(PA_MICRO, [5]) +m4_define(PA_MICRO, [11]) -AC_INIT([pulseaudio], PA_MAJOR.PA_MINOR.PA_MICRO,[mzcbylcnhqvb (at) 0pointer (dot) de]) +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://0pointer.de/lennart/projects/pulseaudio/]) +AC_SUBST(PACKAGE_URL, [http://pulseaudio.org/]) + +AC_SUBST(PA_API_VERSION, 11) +AC_SUBST(PA_PROTOCOL_VERSION, 13) + +# The stable ABI for client applications, for the version info x:y:z +# always will hold y=z +AC_SUBST(LIBPULSE_VERSION_INFO, [5:0:5]) + +# 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]) -AC_SUBST(PA_API_VERSION, 10) -AC_SUBST(PA_PROTOCOL_VERSION, 10) +# 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]) -AC_SUBST(LIBPULSE_VERSION_INFO, [1:0:1]) -AC_SUBST(LIBPULSECORE_VERSION_INFO, [2:0:0]) -AC_SUBST(LIBPULSE_SIMPLE_VERSION_INFO, [0:0:0]) -AC_SUBST(LIBPULSE_BROWSE_VERSION_INFO, [1:0:1]) -AC_SUBST(LIBPULSE_MAINLOOP_GLIB_VERSION_INFO, [0:2:0]) +# 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, [6: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} ***]) @@ -59,9 +77,14 @@ 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 @@ -75,8 +98,8 @@ fi # GCC flags test_gcc_flag() { - AC_LANG_CONFTEST([int main() {}]) - $CC -c conftest.c $CFLAGS $@ > /dev/null 2> /dev/null + 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 @@ -87,30 +110,154 @@ 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" + 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 + if test_gcc_flag $flag ; then CFLAGS="$CFLAGS $flag" AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi - done + done +fi + +# Native atomic operation support +AC_ARG_ENABLE([atomic-arm-linux-helpers], + AC_HELP_STRING([--disable-atomic-arm-linux-helpers], [use inline asm or libatomic_ops instead]), + [ + case "${enableval}" in + yes) atomic_arm_linux_helpers=yes ;; + no) atomic_arm_linux_helpers=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-atomic-arm-linux-helpers) ;; + esac + ], + [atomic_arm_linux_helpers=auto]) + +AC_ARG_ENABLE([atomic-arm-memory-barrier], + AC_HELP_STRING([--enable-atomic-arm-memory-barrier], [only really needed in SMP arm systems]), + [ + case "${enableval}" in + yes) AC_DEFINE_UNQUOTED(ATOMIC_ARM_MEMORY_BARRIER_ENABLED, 1, [Enable memory barriers]) ;; + no) ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-atomic-arm-linux-helpers) ;; + esac + ],) + +AC_MSG_CHECKING([target operating system]) +case $host in + *-*-linux*) + AC_MSG_RESULT([linux]) + pulse_target_os=linux + ;; + *) + AC_MSG_RESULT([unknown]) + pulse_target_os=unknown + ;; +esac + +# If everything else fails use libatomic_ops +need_libatomic_ops=yes + +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]) + need_libatomic_ops=no +else + AC_MSG_RESULT([no]) + # HW specific atomic ops stuff + AC_MSG_CHECKING([architecture for native atomic operations]) + case $host_cpu in + arm*) + AC_MSG_RESULT([arm]) + AC_MSG_CHECKING([whether we can use Linux kernel helpers]) + # The Linux kernel helper functions have been there since 2.6.16. However + # compile time checking for kernel version in cross compile environment + # (which is usually the case for arm cpu) is tricky (or impossible). + if test "x$pulse_target_os" = "xlinux" && test "x$atomic_arm_linux_helpers" != "xno"; then + AC_MSG_RESULT([yes]) + AC_DEFINE_UNQUOTED(ATOMIC_ARM_LINUX_HELPERS, 1, [special arm linux implementation]) + need_libatomic_ops=no + else + AC_MSG_RESULT([no]) + AC_MSG_CHECKING([compiler support for arm inline asm atomic operations]) + AC_LANG_CONFTEST([[int main() + { + volatile int a=0; + int o=0, n=1, r; + asm volatile ("ldrex %0, [%1]\n" + "subs %0, %0, %2\n" + "strexeq %0, %3, [%1]\n" + : "=&r" (r) + : "r" (&a), "Ir" (o), "r" (n) + : "cc"); + return (a==1 ? 0 : -1); + }]]) + $CC conftest.c $CFLAGS -o conftest > /dev/null 2>&1 + ret=$? + rm -f conftest.o conftest + if test $ret -eq 0 ; then + AC_DEFINE([ATOMIC_ARM_INLINE_ASM], 1, [Have ARMv6 instructions.]) + AC_MSG_RESULT([yes]) + need_libatomic_ops=no + else + AC_MSG_RESULT([no]) + fi + fi + ;; + *) + AC_MSG_RESULT([unknown]) + ;; + esac +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_SUBST(LTDLINCL) -AC_SUBST(LIBLTDL) 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([[ @@ -143,9 +290,9 @@ 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 pwd.h sched.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]) + 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 @@ -161,9 +308,6 @@ 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") -# XPG4-UNIX -AC_CHECK_HEADERS([sys/poll.h]) - # Linux AC_CHECK_HEADERS([linux/input.h], [HAVE_EVDEV=1], [HAVE_EVDEV=0]) @@ -179,6 +323,8 @@ 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. #### @@ -191,6 +337,7 @@ AC_CHECK_TYPES(ssize_t, , [AC_DEFINE([ssize_t], [signed long], AC_TYPE_OFF_T AC_TYPE_SIGNAL AC_TYPE_UID_T +AC_CHECK_DECLS(environ) AC_CHECK_DEFINE([SIGXCPU], [signal.h], [ HAVE_SIGXCPU=1 @@ -203,6 +350,10 @@ 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 @@ -212,6 +363,8 @@ AC_SEARCH_LIBS([pow], [m]) 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]) @@ -222,15 +375,22 @@ AC_SEARCH_LIBS([connect], [socket]) # build, disabling its ability to make dlls. AC_CHECK_FUNCS([getopt_long], [], [AC_CHECK_LIB([iberty], [getopt_long])]) +AC_CHECK_LIB(gdbm, gdbm_open) +AC_CHECK_HEADERS(gdbm.h) + #### 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 getaddrinfo getgrgid_r getpwuid_r gettimeofday \ - getuid inet_ntop inet_pton nanosleep pipe posix_memalign setpgid setsid \ - shm_open sigaction sleep sysconf]) +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") @@ -249,11 +409,22 @@ AC_CHECK_FUNCS([lstat]) # Non-standard -AC_CHECK_FUNCS([setresuid setresgid setreuid setregid seteuid setegid]) +AC_CHECK_FUNCS([setresuid setresgid setreuid setregid seteuid setegid ppoll strsignal sig2str strtof_l]) -#### POSIX threads #### +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 -ACX_PTHREAD +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) #### @@ -317,12 +488,6 @@ fi PKG_PROG_PKG_CONFIG -#### Sample rate conversion #### - -PKG_CHECK_MODULES(LIBSAMPLERATE, [ samplerate >= 0.1.0 ]) -AC_SUBST(LIBSAMPLERATE_CFLAGS) -AC_SUBST(LIBSAMPLERATE_LIBS) - #### Sound file #### PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.10 ]) @@ -331,19 +496,60 @@ AC_SUBST(LIBSNDFILE_LIBS) #### atomic-ops ### -AC_CHECK_HEADERS([atomic_ops.h], [], [ -AC_MSG_ERROR([*** libatomic-ops headers not found]) -]) +AC_MSG_CHECKING([whether we need libatomic_ops]) +if test "x$need_libatomic_ops" = "xyes"; then + AC_MSG_RESULT([yes]) + 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 +else + AC_MSG_RESULT([no]) +fi -# 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" +#### 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]), +AC_ARG_ENABLE([oss], + AC_HELP_STRING([--disable-oss], [Disable optional OSS support]), [ case "${enableval}" in yes) oss=yes ;; @@ -375,8 +581,8 @@ 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]), +AC_ARG_ENABLE([alsa], + AC_HELP_STRING([--disable-alsa], [Disable optional ALSA support]), [ case "${enableval}" in yes) alsa=yes ;; @@ -387,7 +593,7 @@ AC_ARG_ENABLE([alsa], [alsa=auto]) if test "x${alsa}" != xno ; then - PKG_CHECK_MODULES(ASOUNDLIB, [ alsa >= 1.0.0 ], + PKG_CHECK_MODULES(ASOUNDLIB, [ alsa >= 1.0.16 ], [ HAVE_ALSA=1 AC_DEFINE([HAVE_ALSA], 1, [Have ALSA?]) @@ -403,14 +609,14 @@ else fi AC_SUBST(ASOUNDLIB_CFLAGS) -AC_SUBST(ASOUNDLIB_LIBS) +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]), +AC_ARG_ENABLE([solaris], + AC_HELP_STRING([--disable-solaris], [Disable optional Solaris audio support]), [ case "${enableval}" in yes) solaris=yes ;; @@ -441,8 +647,8 @@ 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]), +AC_ARG_ENABLE([glib2], + AC_HELP_STRING([--disable-glib2], [Disable optional GLib 2 support]), [ case "${enableval}" in yes) glib2=yes ;; @@ -472,8 +678,8 @@ 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]), +AC_ARG_ENABLE([gconf], + AC_HELP_STRING([--disable-gconf], [Disable optional GConf support]), [ case "${enableval}" in yes) gconf=yes ;; @@ -503,8 +709,8 @@ 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]), +AC_ARG_ENABLE([avahi], + AC_HELP_STRING([--disable-avahi], [Disable optional Avahi support]), [ case "${enableval}" in yes) avahi=yes ;; @@ -540,8 +746,8 @@ AC_SUBST(LIBOIL_LIBS) ### JACK (optional) #### -AC_ARG_ENABLE([jack], - AC_HELP_STRING([--disable-jack], [Disable optional JACK support]), +AC_ARG_ENABLE([jack], + AC_HELP_STRING([--disable-jack], [Disable optional JACK support]), [ case "${enableval}" in yes) jack=yes ;; @@ -571,8 +777,8 @@ 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]), +AC_ARG_ENABLE([asyncns], + AC_HELP_STRING([--disable-asyncns], [Disable optional Async DNS support]), [ case "${enableval}" in yes) asyncns=yes ;; @@ -606,8 +812,8 @@ fi #### TCP wrappers (optional) #### -AC_ARG_ENABLE([tcpwrap], - AC_HELP_STRING([--disable-tcpwrap], [Disable optional TCP wrappers support]), +AC_ARG_ENABLE([tcpwrap], + AC_HELP_STRING([--disable-tcpwrap], [Disable optional TCP wrappers support]), [ case "${enableval}" in yes) tcpwrap=yes ;; @@ -630,8 +836,8 @@ AC_SUBST(LIBWRAP_LIBS) #### LIRC support (optional) #### -AC_ARG_ENABLE([lirc], - AC_HELP_STRING([--disable-lirc], [Disable optional LIRC support]), +AC_ARG_ENABLE([lirc], + AC_HELP_STRING([--disable-lirc], [Disable optional LIRC support]), [ case "${enableval}" in yes) lirc=yes ;; @@ -656,8 +862,8 @@ 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]), +AC_ARG_ENABLE([hal], + AC_HELP_STRING([--disable-hal], [Disable optional HAL support]), [ case "${enableval}" in yes) hal=yes ;; @@ -666,7 +872,6 @@ AC_ARG_ENABLE([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, @@ -685,6 +890,153 @@ 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 >= 0.7 ], + [ + HAVE_POLKIT=1 + 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)])) @@ -723,9 +1075,29 @@ fi AC_SUBST(PA_ACCESS_GROUP) AC_DEFINE_UNQUOTED(PA_ACCESS_GROUP,"$PA_ACCESS_GROUP", [Access group]) +AC_ARG_ENABLE( + per_user_esound_socket, + AS_HELP_STRING([--disable-per-user-esound-socket], [Use global esound socket directory /tmp/.esd/socket.]), + [ + case "${enableval}" in + yes) per_user_esound_socket=1 ;; + no) per_user_esound_socket=0 ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-per-user-esound-socket) ;; + esac + ], + [per_user_esound_socket=1]) + +if test "x$per_user_esound_socket" = "x1"; then + AC_DEFINE([USE_PER_USER_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) +PA_SYSTEM_CONFIG_PATH="${localstatedir}/lib/pulse" +AC_SUBST(PA_SYSTEM_CONFIG_PATH) +PA_SYSTEM_STATE_PATH="${localstatedir}/lib/pulse" +AC_SUBST(PA_SYSTEM_STATE_PATH) ################################### # Output # @@ -767,6 +1139,7 @@ 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 @@ -838,29 +1211,55 @@ 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 + +ENABLE_PER_USER_ESOUND_SOCKET=no +if test "x$per_user_esound_socket" = "x1" ; then + ENABLE_PER_USER_ESOUND_SOCKET=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 TCP Wrappers: ${ENABLE_TCPWRAP} - System User: ${PA_SYSTEM_USER} - System Group: ${PA_SYSTEM_GROUP} - Realtime Group: ${PA_REALTIME_GROUP} - Access Group: ${PA_ACCESS_GROUP} + prefix: ${prefix} + sysconfdir: ${sysconfdir} + localstatedir: ${localstatedir} + System Runtime Path: ${PA_SYSTEM_RUNTIME_PATH} + System State Path: ${PA_SYSTEM_STATE_PATH} + System Config Path: ${PA_SYSTEM_CONFIG_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} + Enable per-user EsounD socket: ${ENABLE_PER_USER_ESOUND_SOCKET} " diff --git a/doxygen/.gitignore b/doxygen/.gitignore new file mode 100644 index 00000000..ca9b4441 --- /dev/null +++ b/doxygen/.gitignore @@ -0,0 +1 @@ +doxygen.conf diff --git a/doxygen/Makefile.am b/doxygen/Makefile.am index c4f66d83..60b00605 100644 --- a/doxygen/Makefile.am +++ b/doxygen/Makefile.am @@ -1,5 +1,3 @@ -# $Id$ -# # This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify diff --git a/doxygen/doxygen.conf.in b/doxygen/doxygen.conf.in index 81923a9f..7ad5d2f3 100644 --- a/doxygen/doxygen.conf.in +++ b/doxygen/doxygen.conf.in @@ -417,7 +417,7 @@ WARN_LOGFILE = # 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 +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/proplist.h ../src/pulse/gccmacro.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 diff --git a/libpulse-browse.pc.in b/libpulse-browse.pc.in index 7b9a59f4..66438b2a 100644 --- a/libpulse-browse.pc.in +++ b/libpulse-browse.pc.in @@ -6,6 +6,6 @@ includedir=${prefix}/include Name: libpulse-browse Description: PulseAudio Network Browsing API Version: @PACKAGE_VERSION@ -Libs: -L${libdir} -lpulse-browse +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 index 0977f185..1a8bfa40 100644 --- a/libpulse-mainloop-glib.pc.in +++ b/libpulse-mainloop-glib.pc.in @@ -6,6 +6,6 @@ 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 +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 index 4ccc3d58..00d1245a 100644 --- a/libpulse-simple.pc.in +++ b/libpulse-simple.pc.in @@ -6,6 +6,6 @@ includedir=${prefix}/include Name: libpulse-simple Description: Simplified Synchronous Client Interface to PulseAudio Version: @PACKAGE_VERSION@ -Libs: -L${libdir} -lpulse-simple +Libs: -L${libdir} -lpulse-simple @PTHREAD_LIBS@ Cflags: -D_REENTRANT -I${includedir} Requires: libpulse diff --git a/libpulse.pc.in b/libpulse.pc.in index 0f01799b..2193b2b9 100644 --- a/libpulse.pc.in +++ b/libpulse.pc.in @@ -8,4 +8,3 @@ Description: Client Interface to PulseAudio Version: @PACKAGE_VERSION@ Libs: -L${libdir} -lpulse @PTHREAD_LIBS@ Cflags: -D_REENTRANT -I${includedir} -Requires: diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 00000000..c213238d --- /dev/null +++ b/man/.gitignore @@ -0,0 +1,4 @@ +*.1 +*.1.xml +*.5 +*.5.xml diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 00000000..31ac69ce --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,190 @@ +# 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 $< > $@ || rm -f $@ + +esdcompat.1: esdcompat.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pax11publish.1: pax11publish.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +paplay.1: paplay.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pacat.1: pacat.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pacmd.1: pacmd.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pactl.1: pactl.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pasuspender.1: pasuspender.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +padsp.1: padsp.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pabrowse.1: pabrowse.1.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pulse-daemon.conf.5: pulse-daemon.conf.5.xml Makefile + xmltoman $< > $@ || rm -f $@ + +pulse-client.conf.5: pulse-client.conf.5.xml Makefile + xmltoman $< > $@ || rm -f $@ + +default.pa.5: default.pa.5.xml Makefile + xmltoman $< > $@ || rm -f $@ + +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..4caad7c9 --- /dev/null +++ b/man/default.pa.5.xml.in @@ -0,0 +1,56 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; + 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..61fefa3a --- /dev/null +++ b/man/esdcompat.1.xml.in @@ -0,0 +1,89 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..33f071be --- /dev/null +++ b/man/pabrowse.1.xml.in @@ -0,0 +1,47 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..7b0d72b9 --- /dev/null +++ b/man/pacat.1.xml.in @@ -0,0 +1,183 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..c20c016e --- /dev/null +++ b/man/pacmd.1.xml.in @@ -0,0 +1,50 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..8d5bf1d3 --- /dev/null +++ b/man/pactl.1.xml.in @@ -0,0 +1,189 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..9bbe3d10 --- /dev/null +++ b/man/padsp.1.xml.in @@ -0,0 +1,110 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..843b1728 --- /dev/null +++ b/man/paplay.1.xml.in @@ -0,0 +1,127 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..52deae68 --- /dev/null +++ b/man/pasuspender.1.xml.in @@ -0,0 +1,80 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..3b40b978 --- /dev/null +++ b/man/pax11publish.1.xml.in @@ -0,0 +1,151 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..ae8de1f8 --- /dev/null +++ b/man/pulse-client.conf.5.xml.in @@ -0,0 +1,113 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; + 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..50e24559 --- /dev/null +++ b/man/pulse-daemon.conf.5.xml.in @@ -0,0 +1,370 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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 <@PACKAGE_BUGREPORT@>; 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..9ce66f87 --- /dev/null +++ b/man/pulseaudio.1.xml.in @@ -0,0 +1,455 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE manpage SYSTEM "xmltoman.dtd"> +<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?> + +<!-- +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>--start</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>--start</opt></p> + + <optdesc><p>Start PulseAudio if it is not running yet. This is + different from starting PulseAudio without <opt>--start</opt> + which would fail if PA is already running. PulseAudio is + guaranteed to be fully initialized when this call + returns. Implies <opt>--daemon</opt>.</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 <@PACKAGE_BUGREPORT@>; 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..113aeeca --- /dev/null +++ b/man/xmltoman.css @@ -0,0 +1,28 @@ +/*** + 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..4760638e --- /dev/null +++ b/man/xmltoman.dtd @@ -0,0 +1,37 @@ +<!-- + 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..766ab254 --- /dev/null +++ b/man/xmltoman.xsl @@ -0,0 +1,127 @@ +<?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. +--> + +<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) > 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) > 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 <ossman@cendio.se> 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/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..f3ed2e2e --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,56 @@ +*.lo +*.o +*.la +.deps +.libs +Makefile +Makefile.in +asyncmsgq-test +asyncq-test +bt-proximity-helper +channelmap-test +client.conf +close-test +cpulimit-test +cpulimit-test2 +daemon.conf +default.pa +envelope-test +esdcompat +flist-test +gconf-helper +get-binary-name-test +hook-list-test +interpol-test +ipacl-test +mainloop-test +mainloop-test-glib +mcalign-test +memblock-test +memblockq-test +mix-test +pabrowse +pacat +pacat-simple +pacmd +pactl +paplay +parec-simple +pasuspender +pax11publish +proplist-test +pulseaudio +queue-test +remix-test +resampler-test +rtpoll-test +rtstutter +sig2str-test +smoother-test +stripnul +strlist-test +sync-playback +thread-mainloop-test +thread-test +utf8-test +voltest diff --git a/src/Makefile.am b/src/Makefile.am index 0ce805ec..9cce6ed4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,9 @@ -# $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 @@ -26,6 +28,7 @@ pulseincludedir=$(includedir)/pulse pulsecoreincludedir=$(includedir)/pulsecore pulseconfdir=$(sysconfdir)/pulse pulselibexecdir=$(libexecdir)/pulse +xdgautostartdir=$(sysconfdir)/xdg/autostart ################################### # Defines # @@ -50,6 +53,8 @@ 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 += -DPA_SYSTEM_CONFIG_PATH=\"$(PA_SYSTEM_CONFIG_PATH)\" +AM_CFLAGS += -DPA_SYSTEM_STATE_PATH=\"$(PA_SYSTEM_STATE_PATH)\" AM_CFLAGS += -DAO_REQUIRE_CAS # This cool debug trap works on i386/gcc only @@ -59,7 +64,7 @@ AM_LIBADD = $(PTHREAD_LIBS) AM_LDADD = $(PTHREAD_LIBS) # Only required on some platforms but defined for all to avoid errors -AM_LDFLAGS = -no-undefined +AM_LDFLAGS = -Wl,-no-undefined -Wl,--gc-sections if STATIC_BINS BINLDFLAGS = -static @@ -72,14 +77,14 @@ endif if OS_IS_WIN32 PA_THREAD_OBJS = \ - pulsecore/once-win32.c pulsecore/once.h \ pulsecore/mutex-win32.c pulsecore/mutex.h \ - pulsecore/thread-win32.c pulsecore/thread.h + pulsecore/thread-win32.c pulsecore/thread.h \ + pulsecore/semaphore-win32.c pulsecore/semaphore.h else PA_THREAD_OBJS = \ - pulsecore/once-posix.c pulsecore/once.h \ pulsecore/mutex-posix.c pulsecore/mutex.h \ - pulsecore/thread-posix.c pulsecore/thread.h + pulsecore/thread-posix.c pulsecore/thread.h \ + pulsecore/semaphore-posix.c pulsecore/semaphore.h endif ################################### @@ -88,19 +93,28 @@ endif 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 + modules/module-defs.h.m4 \ + daemon/pulseaudio-module-xsmp.desktop \ + map-file \ + daemon/org.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 @@ -116,13 +130,12 @@ pulseaudio_SOURCES = \ daemon/cpulimit.c daemon/cpulimit.h \ daemon/daemon-conf.c daemon/daemon-conf.h \ daemon/dumpmodules.c daemon/dumpmodules.h \ - daemon/main.c \ - pulsecore/gccmacro.h + daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \ + daemon/main.c -pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) -pulseaudio_CPPFLAGS = $(AM_CPPFLAGS) -pulseaudio_LDADD = $(AM_LDADD) libpulsecore.la $(LIBLTDL) \ - $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(LIBOIL_LIBS) +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) @@ -138,6 +151,17 @@ else pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -dlopen force $(foreach f,$(PREOPEN_LIBS),-dlopen $(f)) endif +if HAVE_POLKIT + +policy_DATA = daemon/org.pulseaudio.policy + +pulseaudio_SOURCES += daemon/polkit.c daemon/polkit.h +pulseaudio_CFLAGS += $(POLKIT_CFLAGS) +pulseaudio_LDADD += $(POLKIT_LIBS) + + +endif + ################################### # Utility programs # ################################### @@ -145,7 +169,8 @@ endif bin_PROGRAMS += \ pacat \ pactl \ - paplay + paplay \ + pasuspender if HAVE_AF_UNIX bin_PROGRAMS += pacmd @@ -162,8 +187,8 @@ endif bin_SCRIPTS = esdcompat pacat_SOURCES = utils/pacat.c -pacat_LDADD = $(AM_LDADD) libpulse.la -pacat_CFLAGS = $(AM_CFLAGS) +pacat_LDADD = $(AM_LDADD) libpulse.la +pacat_CFLAGS = $(AM_CFLAGS) pacat_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) paplay_SOURCES = utils/paplay.c @@ -171,17 +196,22 @@ paplay_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS) paplay_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) paplay_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -pactl_SOURCES = utils/pactl.c +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) -pacmd_SOURCES = utils/pacmd.c pulsecore/pid.c pulsecore/pid.h +pasuspender_SOURCES = utils/pasuspender.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) +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 +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) @@ -201,6 +231,7 @@ noinst_PROGRAMS = \ pacat-simple \ parec-simple \ strlist-test \ + close-test \ voltest \ memblockq-test \ sync-playback \ @@ -213,7 +244,20 @@ noinst_PROGRAMS = \ hook-list-test \ memblock-test \ thread-test \ - flist-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 \ + rtstutter \ + stripnul if HAVE_SIGXCPU noinst_PROGRAMS += \ @@ -233,7 +277,7 @@ 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_LDADD = $(AM_LDADD) libpulsecore.la libpulse.la thread_mainloop_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) utf8_test_SOURCES = tests/utf8-test.c @@ -241,7 +285,7 @@ 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_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) @@ -258,20 +302,40 @@ 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_SOURCES = tests/memblock-test.c memblock_test_CFLAGS = $(AM_CFLAGS) memblock_test_LDADD = $(AM_LDADD) libpulsecore.la -memblock_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) +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) +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) +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) @@ -293,6 +357,11 @@ strlist_test_CFLAGS = $(AM_CFLAGS) strlist_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la libstrlist.la strlist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) +close_test_SOURCES = tests/close-test.c +close_test_CFLAGS = $(AM_CFLAGS) +close_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la libstrlist.la +close_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + voltest_SOURCES = tests/voltest.c voltest_CFLAGS = $(AM_CFLAGS) voltest_LDADD = $(AM_LDADD) libpulse.la @@ -325,14 +394,59 @@ 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_CFLAGS = $(AM_CFLAGS) sync_playback_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) interpol_test_SOURCES = tests/interpol-test.c -interpol_test_LDADD = $(AM_LDADD) libpulse.la -interpol_test_CFLAGS = $(AM_CFLAGS) +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) libpulsecore.la +proplist_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + +rtstutter_SOURCES = tests/rtstutter.c +rtstutter_LDADD = $(AM_LDADD) libpulsecore.la +rtstutter_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +rtstutter_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + +stripnul_SOURCES = tests/stripnul.c +stripnul_LDADD = $(AM_LDADD) libpulsecore.la +stripnul_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +stripnul_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + ################################### # Client library # ################################### @@ -360,7 +474,9 @@ pulseinclude_HEADERS = \ pulse/util.h \ pulse/version.h \ pulse/volume.h \ - pulse/xmalloc.h + pulse/xmalloc.h \ + pulse/proplist.h \ + pulse/gccmacro.h if HAVE_AVAHI pulseinclude_HEADERS += \ @@ -410,7 +526,8 @@ libpulse_la_SOURCES = \ 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/xmalloc.c pulse/xmalloc.h \ + pulse/proplist.c pulse/proplist.h # Internal stuff that is shared with libpulsecore libpulse_la_SOURCES += \ @@ -418,7 +535,6 @@ libpulse_la_SOURCES += \ 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 \ @@ -448,12 +564,17 @@ libpulse_la_SOURCES += \ pulsecore/winsock.h pulsecore/creds.h \ pulsecore/shm.c pulsecore/shm.h \ pulsecore/flist.c pulsecore/flist.h \ - pulsecore/anotify.c pulsecore/anotify.h \ + pulsecore/object.c pulsecore/object.h \ + pulsecore/msgobject.c pulsecore/msgobject.h \ + pulsecore/once.c pulsecore/once.h \ + pulsecore/rtclock.c pulsecore/rtclock.h \ + pulsecore/time-smoother.c pulsecore/time-smoother.h \ + pulsecore/proplist-util.c pulsecore/proplist-util.h \ $(PA_THREAD_OBJS) if OS_IS_WIN32 libpulse_la_SOURCES += \ - pulsecore/dllmain.c + pulsecore/dllmain.c endif if HAVE_X11 @@ -463,7 +584,7 @@ libpulse_la_SOURCES += \ endif libpulse_la_CFLAGS = $(AM_CFLAGS) -libpulse_la_LDFLAGS = -version-info $(LIBPULSE_VERSION_INFO) +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 @@ -476,20 +597,33 @@ libpulse_la_CFLAGS += $(LIBASYNCNS_CFLAGS) libpulse_la_LIBADD += $(LIBASYNCNS_LIBS) endif -libpulse_simple_la_SOURCES = pulse/simple.c pulse/simple.h +libpulse_simple_la_SOURCES = \ + pulse/simple.c pulse/simple.h \ + pulsecore/log.c pulsecore/log.h \ + pulsecore/core-util.c pulsecore/core-util.h \ + pulsecore/core-error.c pulsecore/core-error.h \ + pulsecore/once.c pulsecore/once.h \ + $(PA_THREAD_OBJS) + libpulse_simple_la_CFLAGS = $(AM_CFLAGS) libpulse_simple_la_LIBADD = $(AM_LIBADD) libpulse.la -libpulse_simple_la_LDFLAGS = -version-info $(LIBPULSE_SIMPLE_VERSION_INFO) +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 +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) +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_SOURCES = \ + pulse/glib-mainloop.h pulse/glib-mainloop.c \ + pulsecore/log.c pulsecore/log.h \ + pulsecore/core-util.c pulsecore/core-util.h \ + pulsecore/core-error.c pulsecore/core-error.h \ + pulsecore/once.c pulsecore/once.h \ + $(PA_THREAD_OBJS) 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) +libpulse_mainloop_glib_la_LDFLAGS = -version-info $(LIBPULSE_MAINLOOP_GLIB_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file ################################### # OSS emulation # @@ -503,22 +637,38 @@ bin_SCRIPTS += utils/padsp endif -libpulsedsp_la_SOURCES = utils/padsp.c +libpulsedsp_la_SOURCES = utils/padsp.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) 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 = \ +#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 \ @@ -560,6 +710,7 @@ pulsecoreinclude_HEADERS = \ pulsecore/refcnt.h \ pulsecore/mutex.h \ pulsecore/thread.h \ + pulsecore/semaphore.h \ pulsecore/once.h lib_LTLIBRARIES += libpulsecore.la @@ -576,7 +727,8 @@ libpulsecore_la_SOURCES = \ 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/xmalloc.c pulse/xmalloc.h \ + pulse/proplist.c pulse/proplist.h # Pure core stuff (some are shared in libpulse though). libpulsecore_la_SOURCES += \ @@ -601,6 +753,7 @@ libpulsecore_la_SOURCES += \ 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 \ @@ -630,23 +783,38 @@ libpulsecore_la_SOURCES += \ pulsecore/hook-list.c pulsecore/hook-list.h \ pulsecore/shm.c pulsecore/shm.h \ pulsecore/flist.c pulsecore/flist.h \ - pulsecore/anotify.c pulsecore/anotify.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 \ + pulsecore/proplist-util.c pulsecore/proplist-util.h \ $(PA_THREAD_OBJS) if OS_IS_WIN32 libpulsecore_la_SOURCES += \ - pulsecore/dllmain.c + 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) +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 += \ +#pulsecoreinclude_HEADERS += +noinst_HEADERS += \ pulsecore/socket-util.h \ pulsecore/iochannel.h \ pulsecore/socket-server.h \ @@ -695,9 +863,9 @@ modlibexec_LTLIBRARIES = \ libauthkey-prop.la \ libstrlist.la \ libprotocol-simple.la \ - libprotocol-esound.la \ + libprotocol-http.la \ libprotocol-native.la \ - libprotocol-http.la + libprotocol-esound.la # We need to emulate sendmsg/recvmsg to support this on Win32 if !OS_IS_WIN32 @@ -706,7 +874,8 @@ modlibexec_LTLIBRARIES += \ endif if HAVE_X11 -pulsecoreinclude_HEADERS += \ +#pulsecoreinclude_HEADERS += +noinst_HEADERS += \ pulsecore/x11wrap.h \ pulsecore/x11prop.h @@ -716,7 +885,8 @@ modlibexec_LTLIBRARIES += \ endif if HAVE_AVAHI -pulsecoreinclude_HEADERS += \ +#pulsecoreinclude_HEADERS += +noinst_HEADERS += \ pulsecore/avahi-wrap.h modlibexec_LTLIBRARIES += \ @@ -754,7 +924,7 @@ libpstream_la_LIBADD = $(AM_LIBADD) libpulsecore.la libpacket.la libiochannel.la 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 +libpstream_util_la_LIBADD = $(AM_LIBADD) libpacket.la libpstream.la libtagstruct.la libpulsecore.la libpdispatch_la_SOURCES = pulsecore/pdispatch.c pulsecore/pdispatch.h libpdispatch_la_LDFLAGS = -avoid-version @@ -815,7 +985,7 @@ libsocket_util_la_SOURCES = \ 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_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 @@ -846,19 +1016,27 @@ modlibexec_LTLIBRARIES += \ module-cli.la \ module-cli-protocol-tcp.la \ module-simple-protocol-tcp.la \ - module-esound-protocol-tcp.la \ + module-null-sink.la \ + module-detect.la \ + module-volume-restore.la \ + module-device-restore.la \ + module-default-device-restore.la \ + module-always-sink.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-sine.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 \ - module-null-sink.la \ - module-esound-sink.la \ - module-http-protocol-tcp.la \ - module-detect.la \ - module-volume-restore.la \ - module-rescue-streams.la + module-position-event-sounds.la + # See comment at librtp.la above if !OS_IS_WIN32 @@ -871,9 +1049,9 @@ if HAVE_AF_UNIX modlibexec_LTLIBRARIES += \ module-cli-protocol-unix.la \ module-simple-protocol-unix.la \ - module-esound-protocol-unix.la \ + module-http-protocol-unix.la \ module-native-protocol-unix.la \ - module-http-protocol-unix.la + module-esound-protocol-unix.la endif if HAVE_MKFIFO @@ -896,14 +1074,14 @@ endif if HAVE_X11 modlibexec_LTLIBRARIES += \ module-x11-bell.la \ - module-x11-publish.la + module-x11-publish.la \ + module-x11-xsmp.la endif if HAVE_OSS modlibexec_LTLIBRARIES += \ liboss-util.la \ - module-oss.la \ - module-oss-mmap.la + module-oss.la endif if HAVE_ALSA @@ -920,7 +1098,8 @@ endif if HAVE_AVAHI modlibexec_LTLIBRARIES += \ - module-zeroconf-publish.la + module-zeroconf-publish.la \ + module-zeroconf-discover.la endif if HAVE_LIRC @@ -939,18 +1118,20 @@ modlibexec_LTLIBRARIES += \ module-jack-source.la endif +pulselibexec_PROGRAMS = + if HAVE_GCONF modlibexec_LTLIBRARIES += \ module-gconf.la -pulselibexec_PROGRAMS = \ +pulselibexec_PROGRAMS += \ gconf-helper endif -if OS_IS_WIN32 -modlibexec_LTLIBRARIES += \ - module-waveout.la -endif +#if OS_IS_WIN32 +#modlibexec_LTLIBRARIES += \ +# module-waveout.la +#endif if HAVE_HAL modlibexec_LTLIBRARIES += \ @@ -958,6 +1139,20 @@ modlibexec_LTLIBRARIES += \ module-hal-detect.la endif +if HAVE_DBUS +modlibexec_LTLIBRARIES += \ + libdbus-util.la \ + module-console-kit.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 = \ @@ -975,6 +1170,8 @@ SYMDEF_FILES = \ 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 \ @@ -983,14 +1180,15 @@ SYMDEF_FILES = \ 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-oss-mmap-symdef.h \ modules/module-alsa-sink-symdef.h \ modules/module-alsa-source-symdef.h \ modules/module-solaris-symdef.h \ @@ -1001,17 +1199,24 @@ SYMDEF_FILES = \ modules/module-jack-sink-symdef.h \ modules/module-jack-source-symdef.h \ modules/module-volume-restore-symdef.h \ + modules/module-device-restore-symdef.h \ + modules/module-default-device-restore-symdef.h \ + modules/module-always-sink-symdef.h \ modules/module-rescue-streams-symdef.h \ + modules/module-suspend-on-idle-symdef.h \ modules/module-hal-detect-symdef.h \ - modules/gconf/module-gconf-symdef.h + modules/module-bt-proximity-symdef.h \ + modules/gconf/module-gconf-symdef.h \ + modules/module-position-event-sounds-symdef.h \ + modules/module-console-kit-symdef.h EXTRA_DIST += $(SYMDEF_FILES) BUILT_SOURCES += $(SYMDEF_FILES) $(SYMDEF_FILES): modules/module-defs.h.m4 - mkdir modules - mkdir modules/gconf - mkdir modules/rtp + $(MKDIR_P) modules + $(MKDIR_P) modules/gconf + $(MKDIR_P) modules/rtp $(M4) -Dfname="$@" $< > $@ # Simple protocol @@ -1093,7 +1298,7 @@ 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 +module_esound_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la libsocket-client.la libauthkey.la libsocket-util.la # Pipes @@ -1121,6 +1326,15 @@ 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:/usr/local/lib64/ladspa:/usr/lib64/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 @@ -1144,7 +1358,12 @@ module_x11_bell_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA 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 +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) libx11wrap.la libpulsecore.la # OSS @@ -1154,11 +1373,7 @@ 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 - -module_oss_mmap_la_SOURCES = modules/module-oss-mmap.c -module_oss_mmap_la_LDFLAGS = -module -avoid-version -module_oss_mmap_la_LIBADD = $(AM_LIBADD) liboss-util.la libpulsecore.la +module_oss_la_LIBADD = $(AM_LIBADD) libiochannel.la liboss-util.la libpulsecore.la # ALSA @@ -1181,7 +1396,7 @@ module_alsa_source_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) module_solaris_la_SOURCES = modules/module-solaris.c module_solaris_la_LDFLAGS = -module -avoid-version -module_solaris_la_LIBADD = $(AM_LIBADD) libiochannel.la +module_solaris_la_LIBADD = $(AM_LIBADD) libiochannel.la libpulsecore.la # Avahi @@ -1190,6 +1405,11 @@ 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 @@ -1206,10 +1426,10 @@ 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) +#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 @@ -1223,16 +1443,46 @@ module_volume_restore_la_LDFLAGS = -module -avoid-version module_volume_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la module_volume_restore_la_CFLAGS = $(AM_CFLAGS) +# Position event sounds in space +module_position_event_sounds_la_SOURCES = modules/module-position-event-sounds.c +module_position_event_sounds_la_LDFLAGS = -module -avoid-version +module_position_event_sounds_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_position_event_sounds_CFLAGS = $(AM_CFLAGS) + +# Device volume restore module +module_device_restore_la_SOURCES = modules/module-device-restore.c +module_device_restore_la_LDFLAGS = -module -avoid-version +module_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la -lgdbm +module_device_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) + +# Always Sink module +module_always_sink_la_SOURCES = modules/module-always-sink.c +module_always_sink_la_LDFLAGS = -module -avoid-version +module_always_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_always_sink_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 +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 @@ -1245,24 +1495,29 @@ module_rtp_recv_la_CFLAGS = $(AM_CFLAGS) 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_LIBADD) $(JACK_CFLAGS) +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_LIBADD) $(JACK_CFLAGS) +module_jack_source_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS) -# HAL +# HAL/D-Bus libdbus_util_la_SOURCES = modules/dbus-util.c modules/dbus-util.h libdbus_util_la_LDFLAGS = -avoid-version -libdbus_util_la_LIBADD = $(AM_LIBADD) $(HAL_LIBS) libpulsecore.la -libdbus_util_la_CFLAGS = $(AM_CFLAGS) $(HAL_CFLAGS) +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) +module_console_kit_la_SOURCES = modules/module-console-kit.c +module_console_kit_la_LDFLAGS = -module -avoid-version +module_console_kit_la_LIBADD = $(AM_LIBADD) $(HAL_LIBS) libpulsecore.la libdbus-util.la +module_console_kit_la_CFLAGS = $(AM_CFLAGS) $(HAL_CFLAGS) + # GConf support module_gconf_la_SOURCES = modules/gconf/module-gconf.c module_gconf_la_LDFLAGS = -module -avoid-version @@ -1270,17 +1525,28 @@ 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) +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: pulse - chown root $< - chmod u+s $< +suid: pulseaudio .libs/lt-pulseaudio + chown root $^ + chmod u+s $^ CLEANFILES = esdcompat client.conf default.pa daemon.conf @@ -1293,14 +1559,14 @@ esdcompat: daemon/esdcompat.in Makefile client.conf: pulse/client.conf.in Makefile sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@ -if OS_IS_WIN32 +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,@HAVE_HAL_TRUE\@,@HAVE_HAL_TRUE@,g' \ - -e 's,@HAVE_HAL_FALSE\@,@HAVE_HAL_FALSE@,g' < $< > $@ + -e 's,@PA_DLSEARCHPATH\@,$(modlibexecdir),g' \ + -e 's,@PA_SOEXT\@,.so,g' < $< > $@ endif daemon.conf: daemon/daemon.conf.in Makefile @@ -1310,12 +1576,32 @@ daemon.conf: daemon/daemon.conf.in Makefile 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/caps.c b/src/daemon/caps.c index cebdaebc..ae07119c 100644 --- a/src/daemon/caps.c +++ b/src/daemon/caps.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,15 +24,18 @@ #include <config.h> #endif -#include <assert.h> #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> @@ -50,20 +54,23 @@ int setresuid(uid_t r, uid_t e, uid_t s); /* 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 rights."); + pa_log_info("Dropping root priviliges."); #if defined(HAVE_SETRESUID) - setresuid(uid, uid, uid); + pa_assert_se(setresuid(uid, uid, uid) >= 0); #elif defined(HAVE_SETREUID) - setreuid(uid, uid); + pa_assert_se(setreuid(uid, uid) >= 0); #else - setuid(uid); - seteuid(uid); + pa_assert_se(setuid(uid) >= 0); + pa_assert_se(seteuid(uid) >= 0); #endif + + pa_assert_se(getuid() == uid); + pa_assert_se(geteuid() == uid); } #else @@ -73,77 +80,68 @@ void pa_drop_root(void) { #endif -#ifdef HAVE_SYS_CAPABILITY_H +#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_SYS_PRCTL_H) -/* Limit capabilities set to CAPSYS_NICE */ -int pa_limit_caps(void) { - int r = -1; +/* Limit permitted capabilities set to CAPSYS_NICE */ +void pa_limit_caps(void) { cap_t caps; cap_value_t nice_cap = CAP_SYS_NICE; - /* Only drop caps when called SUID */ - if (getuid() == 0) - return 0; - - caps = cap_init(); - 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); + pa_assert_se(caps = cap_init()); + pa_assert_se(cap_clear(caps) == 0); + pa_assert_se(cap_set_flag(caps, CAP_EFFECTIVE, 1, &nice_cap, CAP_SET) == 0); + pa_assert_se(cap_set_flag(caps, CAP_PERMITTED, 1, &nice_cap, CAP_SET) == 0); if (cap_set_proc(caps) < 0) - goto fail; + /* Hmm, so we couldn't limit our caps, which probably means we + * hadn't any in the first place, so let's just make sure of + * that */ + pa_drop_caps(); + else + pa_log_info("Limited capabilities successfully to CAP_SYS_NICE."); - pa_log_info("dropped capabilities successfully."); - - r = 0; + pa_assert_se(cap_free(caps) == 0); -fail: - cap_free (caps); - - return r; + pa_assert_se(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0); } /* Drop all capabilities, effectively becoming a normal user */ -int pa_drop_caps(void) { +void pa_drop_caps(void) { cap_t caps; - int r = -1; - /* Only drop caps when called SUID */ - if (getuid() == 0) - return 0; + pa_assert_se(prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) == 0); - caps = cap_init(); - assert(caps); + pa_assert_se(caps = cap_init()); + pa_assert_se(cap_clear(caps) == 0); + pa_assert_se(cap_set_proc(caps) == 0); + pa_assert_se(cap_free(caps) == 0); - cap_clear(caps); + pa_assert_se(!pa_have_caps()); +} - if (cap_set_proc(caps) < 0) { - pa_log("failed to drop capabilities: %s", pa_cstrerror(errno)); - goto fail; - } - - r = 0; +pa_bool_t pa_have_caps(void) { + cap_t caps; + cap_flag_value_t flag = CAP_CLEAR; -fail: - cap_free (caps); - - return r; + pa_assert_se(caps = cap_get_proc()); + pa_assert_se(cap_get_flag(caps, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0); + pa_assert_se(cap_free(caps) == 0); + + return flag == CAP_SET; } #else /* NOOPs in case capabilities are not available. */ -int pa_limit_caps(void) { - return 0; +void pa_limit_caps(void) { } -int pa_drop_caps(void) { +void pa_drop_caps(void) { pa_drop_root(); - return 0; } -#endif +pa_bool_t pa_have_caps(void) { + return FALSE; +} +#endif diff --git a/src/daemon/caps.h b/src/daemon/caps.h index 8a618286..176aa90e 100644 --- a/src/daemon/caps.h +++ b/src/daemon/caps.h @@ -1,29 +1,32 @@ #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. ***/ +#include <pulsecore/macro.h> + void pa_drop_root(void); -int pa_limit_caps(void); -int pa_drop_caps(void); +void pa_drop_caps(void); +void pa_limit_caps(void); +pa_bool_t pa_have_caps(void); #endif diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c index d368b644..4b2466ce 100644 --- a/src/daemon/cmdline.c +++ b/src/daemon/cmdline.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <string.h> -#include <assert.h> #include <stdlib.h> #include <stdio.h> #include <getopt.h> @@ -34,6 +33,7 @@ #include <pulsecore/core-util.h> #include <pulsecore/strbuf.h> +#include <pulsecore/macro.h> #include "cmdline.h" @@ -47,6 +47,7 @@ enum { ARG_FAIL, ARG_LOG_LEVEL, ARG_HIGH_PRIORITY, + ARG_REALTIME, ARG_DISALLOW_MODULE_LOADING, ARG_EXIT_IDLE_TIME, ARG_MODULE_IDLE_TIME, @@ -61,11 +62,14 @@ enum { ARG_CHECK, ARG_NO_CPU_LIMIT, ARG_DISABLE_SHM, - ARG_SYSTEM + ARG_DUMP_RESAMPLE_METHODS, + ARG_SYSTEM, + ARG_CLEANUP_SHM, + ARG_START }; /* Tabel for getopt_long() */ -static struct option long_options[] = { +static const struct option long_options[] = { {"help", 0, 0, ARG_HELP}, {"version", 0, 0, ARG_VERSION}, {"dump-conf", 0, 0, ARG_DUMP_CONF}, @@ -75,6 +79,7 @@ static struct option long_options[] = { {"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}, @@ -85,28 +90,36 @@ static struct option long_options[] = { {"dl-search-path", 1, 0, ARG_DL_SEARCH_PATH}, {"resample-method", 1, 0, ARG_RESAMPLE_METHOD}, {"kill", 0, 0, ARG_KILL}, + {"start", 0, 0, ARG_START}, {"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" + " --start Start the daemon if it is not running\n" " -k --kill Kill a running daemon\n" " --check Check for a running daemon\n\n" @@ -114,8 +127,12 @@ void pa_cmdline_help(const char *argv0) { " --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 process priority\n" - " (only available as root)\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" @@ -124,14 +141,13 @@ void pa_cmdline_help(const char *argv0) { " --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" + " -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" - " (one of src-sinc-medium-quality,\n" - " src-sinc-best-quality,src-sinc-fastest\n" - " src-zero-order-hold,src-linear,trivial)\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" @@ -143,20 +159,23 @@ void pa_cmdline_help(const char *argv0) { " -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; - assert(conf && argc && argv); + + 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: @@ -176,39 +195,55 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d 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_START: + conf->cmd = PA_CMD_START; + conf->daemonize = TRUE; + 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': - pa_strbuf_printf(buf, ".include %s\n", optarg); + 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) : 1) < 0) { + 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) : 1) < 0) { + if ((conf->fail = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { pa_log("--fail expects boolean argument"); goto fail; } @@ -226,39 +261,45 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d 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) : 1) < 0) { + 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) : 1) < 0) { + 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) : 1) < 0) { + 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; + conf->load_default_script_file = FALSE; break; case ARG_LOG_TARGET: @@ -288,26 +329,26 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d break; case ARG_SYSTEM: - if ((conf->system_instance = optarg ? pa_parse_boolean(optarg) : 1) < 0) { + 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) : 1) < 0) { + 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) : 1) < 0) { + if ((conf->disable_shm = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { pa_log("--disable-shm expects boolean argument"); goto fail; } break; - + default: goto fail; } @@ -322,12 +363,12 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d } *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 index 25453e55..fd72a6d3 100644 --- a/src/daemon/cmdline.h +++ b/src/daemon/cmdline.h @@ -1,21 +1,21 @@ #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 diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c index d7466b06..42a71f7e 100644 --- a/src/daemon/cpulimit.c +++ b/src/daemon/cpulimit.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -28,6 +28,7 @@ #include <pulsecore/core-util.h> #include <pulsecore/core-error.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "cpulimit.h" @@ -36,7 +37,6 @@ #include <errno.h> #include <stdio.h> #include <string.h> -#include <assert.h> #include <sys/time.h> #include <unistd.h> #include <signal.h> @@ -80,7 +80,7 @@ static pa_io_event *io_event = NULL; static struct sigaction sigaction_prev; /* Nonzero after pa_cpu_limit_init() */ -static int installed = 0; +static pa_bool_t installed = FALSE; /* The current state of operation */ static enum { @@ -90,23 +90,18 @@ static enum { /* Reset the SIGXCPU timer to the next t seconds */ static void reset_cpu_time(int t) { - int r; long n; struct rlimit rl; struct rusage ru; /* Get the current CPU time of the current process */ - r = getrusage(RUSAGE_SELF, &ru); - assert(r >= 0); + pa_assert_se(getrusage(RUSAGE_SELF, &ru) >= 0); n = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + t; - - r = getrlimit(RLIMIT_CPU, &rl); - assert(r >= 0); + pa_assert_se(getrlimit(RLIMIT_CPU, &rl) >= 0); rl.rlim_cur = n; - r = setrlimit(RLIMIT_CPU, &rl); - assert(r >= 0); + pa_assert_se(setrlimit(RLIMIT_CPU, &rl) >= 0); } /* A simple, thread-safe puts() work-alike */ @@ -116,7 +111,10 @@ static void write_err(const char *p) { /* The signal handler, called on every SIGXCPU */ static void signal_handler(int sig) { - assert(sig == SIGXCPU); + int saved_errno; + + saved_errno = errno; + pa_assert(sig == SIGXCPU); if (phase == PHASE_IDLE) { time_t now; @@ -128,37 +126,44 @@ static void signal_handler(int sig) { time(&now); #ifdef PRINT_CPU_LOAD - snprintf(t, sizeof(t), "Using %0.1f%% CPU\n", (double)CPUTIME_INTERVAL_SOFT/(now-last_time)*100); + 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 */ + abort(); /* 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; - assert(m && e && f == PA_IO_EVENT_INPUT && e == io_event && fd == the_pipe[0]); + 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 */ } @@ -166,8 +171,14 @@ static void callback(pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags /* Initializes CPU load limiter */ int pa_cpu_limit_init(pa_mainloop_api *m) { struct sigaction sa; - assert(m && !api && !io_event && the_pipe[0] == -1 && the_pipe[1] == -1 && !installed); - + + 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 */ @@ -176,10 +187,10 @@ int pa_cpu_limit_init(pa_mainloop_api *m) { return -1; } - pa_make_nonblock_fd(the_pipe[0]); - pa_make_nonblock_fd(the_pipe[1]); - pa_fd_set_cloexec(the_pipe[0], 1); - pa_fd_set_cloexec(the_pipe[1], 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); @@ -191,40 +202,34 @@ int pa_cpu_limit_init(pa_mainloop_api *m) { 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; + installed = TRUE; reset_cpu_time(CPUTIME_INTERVAL_SOFT); - + return 0; } /* Shutdown CPU load limiter */ void pa_cpu_limit_done(void) { - int r; if (io_event) { - assert(api); + pa_assert(api); api->io_free(io_event); io_event = NULL; api = NULL; } - if (the_pipe[0] >= 0) - close(the_pipe[0]); - if (the_pipe[1] >= 0) - close(the_pipe[1]); - the_pipe[0] = the_pipe[1] = -1; + pa_close_pipe(the_pipe); if (installed) { - r = sigaction(SIGXCPU, &sigaction_prev, NULL); - assert(r >= 0); - installed = 0; + pa_assert_se(sigaction(SIGXCPU, &sigaction_prev, NULL) >= 0); + installed = FALSE; } } diff --git a/src/daemon/cpulimit.h b/src/daemon/cpulimit.h index 21bdd17b..cb9a123d 100644 --- a/src/daemon/cpulimit.h +++ b/src/daemon/cpulimit.h @@ -1,21 +1,21 @@ #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 diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index dd478126..9ac40901 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -1,8 +1,9 @@ -/* $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, @@ -26,29 +27,27 @@ #include <errno.h> #include <stdio.h> #include <string.h> -#include <assert.h> #include <unistd.h> +#include <sched.h> #include <pulse/xmalloc.h> +#include <pulse/timeval.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" -#ifndef OS_IS_WIN32 -# define PATH_SEP "/" -#else -# define PATH_SEP "\\" -#endif +#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_SYSTEM_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "system.pa" -#define DEFAULT_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PATH_SEP "default.pa" -#define DEFAULT_SCRIPT_FILE_USER PATH_SEP "default.pa" -#define DEFAULT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PATH_SEP "daemon.conf" -#define DEFAULT_CONFIG_FILE_USER PATH_SEP "daemon.conf" +#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" @@ -56,54 +55,77 @@ static const pa_daemon_conf default_conf = { .cmd = PA_CMD_DAEMON, - .daemonize = 0, - .fail = 1, - .high_priority = 0, - .disallow_module_loading = 0, + .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, + .load_default_script_file = TRUE, .default_script_file = NULL, .log_target = PA_LOG_SYSLOG, .log_level = PA_LOG_NOTICE, - .resample_method = PA_RESAMPLER_SRC_SINC_FASTEST, + .resample_method = PA_RESAMPLER_AUTO, + .disable_remixing = FALSE, .config_file = NULL, - .use_pid_file = 1, - .system_instance = 0, - .no_cpu_limit = 0, - .disable_shm = 0 + .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 = 0 }, - .rlimit_core = { .value = 0, .is_set = 0 }, - .rlimit_data = { .value = 0, .is_set = 0 }, - .rlimit_fsize = { .value = 0, .is_set = 0 }, - .rlimit_nofile = { .value = 200, .is_set = 1 }, - .rlimit_stack = { .value = 0, .is_set = 0 } + ,.rlimit_fsize = { .value = 0, .is_set = FALSE }, + .rlimit_data = { .value = 0, .is_set = FALSE }, + .rlimit_stack = { .value = 0, .is_set = FALSE }, + .rlimit_core = { .value = 0, .is_set = FALSE }, + .rlimit_rss = { .value = 0, .is_set = FALSE } #ifdef RLIMIT_NPROC - , .rlimit_nproc = { .value = 0, .is_set = 0 } + ,.rlimit_nproc = { .value = 0, .is_set = FALSE } #endif + ,.rlimit_nofile = { .value = 256, .is_set = TRUE } #ifdef RLIMIT_MEMLOCK - , .rlimit_memlock = { .value = 0, .is_set = 1 } + ,.rlimit_memlock = { .value = 0, .is_set = FALSE } +#endif + ,.rlimit_as = { .value = 0, .is_set = FALSE } +#ifdef RLIMIT_LOCKS + ,.rlimit_locks = { .value = 0, .is_set = FALSE } +#endif +#ifdef RLIMIT_SIGPENDING + ,.rlimit_sigpending = { .value = 0, .is_set = FALSE } +#endif +#ifdef RLIMIT_MSGQUEUE + ,.rlimit_msgqueue = { .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 +#ifdef RLIMIT_RTTIME + ,.rlimit_rttime = { .value = PA_USEC_PER_SEC, .is_set = TRUE } #endif #endif }; pa_daemon_conf* pa_daemon_conf_new(void) { - FILE *f; - pa_daemon_conf *c = pa_xmemdup(&default_conf, sizeof(default_conf)); - - if ((f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file, "r"))) - fclose(f); + pa_daemon_conf *c = pa_xnewdup(pa_daemon_conf, &default_conf, 1); c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH); return c; } void pa_daemon_conf_free(pa_daemon_conf *c) { - assert(c); + pa_assert(c); pa_xfree(c->script_commands); pa_xfree(c->dl_search_path); pa_xfree(c->default_script_file); @@ -112,7 +134,8 @@ void pa_daemon_conf_free(pa_daemon_conf *c) { } int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) { - assert(c && string); + pa_assert(c); + pa_assert(string); if (!strcmp(string, "auto")) c->auto_log_target = 1; @@ -130,7 +153,8 @@ 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) { uint32_t u; - assert(c && string); + pa_assert(c); + pa_assert(string); if (pa_atou(string, &u) >= 0) { if (u >= PA_LOG_LEVEL_MAX) @@ -155,7 +179,8 @@ 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) { int m; - assert(c && string); + pa_assert(c); + pa_assert(string); if ((m = pa_parse_resample_method(string)) < 0) return -1; @@ -166,7 +191,11 @@ int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string) { 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; - assert(filename && lvalue && rvalue && 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); @@ -178,7 +207,11 @@ static int parse_log_target(const char *filename, unsigned line, const char *lva 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; - assert(filename && lvalue && rvalue && 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); @@ -190,10 +223,14 @@ static int parse_log_level(const char *filename, unsigned line, const char *lval 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; - assert(filename && lvalue && rvalue && 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] Inavalid resample method '%s'.", filename, line, rvalue); + pa_log("[%s:%u] Invalid resample method '%s'.", filename, line, rvalue); return -1; } @@ -203,10 +240,11 @@ static int parse_resample_method(const char *filename, unsigned line, const char 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; - assert(filename); - assert(lvalue); - assert(rvalue); - assert(r); + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(r); if (rvalue[strspn(rvalue, "\t ")] == 0) { /* Empty string */ @@ -215,7 +253,7 @@ static int parse_rlimit(const char *filename, unsigned line, const char *lvalue, } else { int32_t k; if (pa_atoi(rvalue, &k) < 0) { - pa_log("[%s:%u] Inavalid rlimit '%s'.", filename, line, rvalue); + pa_log("[%s:%u] Invalid rlimit '%s'.", filename, line, rvalue); return -1; } r->is_set = k >= 0; @@ -228,104 +266,313 @@ static int parse_rlimit(const char *filename, unsigned line, const char *lvalue, 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 > (int32_t) 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 > (int32_t) 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 }, - { "disallow-module-loading", 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 }, - { "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 }, - { "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 }, + { "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 }, + { "load-default-script-file", 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 }, + { "rlimit-fsize", parse_rlimit, NULL }, + { "rlimit-data", parse_rlimit, NULL }, + { "rlimit-stack", parse_rlimit, NULL }, + { "rlimit-core", parse_rlimit, NULL }, + { "rlimit-rss", parse_rlimit, NULL }, + { "rlimit-nofile", parse_rlimit, NULL }, + { "rlimit-as", parse_rlimit, NULL }, #ifdef RLIMIT_NPROC - { "rlimit-nproc", parse_rlimit, NULL }, + { "rlimit-nproc", parse_rlimit, NULL }, #endif #ifdef RLIMIT_MEMLOCK - { "rlimit-memlock", parse_rlimit, NULL }, + { "rlimit-memlock", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_LOCKS + { "rlimit-locks", parse_rlimit, NULL }, #endif +#ifdef RLIMIT_SIGPENDING + { "rlimit-sigpending", parse_rlimit, NULL }, #endif - { NULL, NULL, NULL }, +#ifdef RLIMIT_MSGQUEUE + { "rlimit-msgqueue", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_NICE + { "rlimit-nice", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_RTPRIO + { "rlimit-rtprio", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_RTTIME + { "rlimit-rttime", 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->disallow_module_loading; - table[4].data = &c->exit_idle_time; - table[5].data = &c->module_idle_time; - table[6].data = &c->scache_idle_time; - table[7].data = &c->dl_search_path; - table[8].data = &c->default_script_file; - table[9].data = c; - table[10].data = c; - table[11].data = c; + 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->use_pid_file; - table[14].data = &c->system_instance; - table[15].data = &c->no_cpu_limit; - table[16].data = &c->disable_shm; + 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; + table[26].data = &c->load_default_script_file; #ifdef HAVE_SYS_RESOURCE_H - table[17].data = &c->rlimit_as; - table[18].data = &c->rlimit_core; - table[19].data = &c->rlimit_data; - table[20].data = &c->rlimit_fsize; - table[21].data = &c->rlimit_nofile; - table[22].data = &c->rlimit_stack; + table[27].data = &c->rlimit_fsize; + table[28].data = &c->rlimit_data; + table[29].data = &c->rlimit_stack; + table[30].data = &c->rlimit_as; + table[31].data = &c->rlimit_core; + table[32].data = &c->rlimit_nofile; + table[33].data = &c->rlimit_as; #ifdef RLIMIT_NPROC - table[23].data = &c->rlimit_nproc; + table[34].data = &c->rlimit_nproc; #endif + #ifdef RLIMIT_MEMLOCK #ifndef RLIMIT_NPROC #error "Houston, we have a numbering problem!" #endif - table[24].data = &c->rlimit_memlock; + table[35].data = &c->rlimit_memlock; +#endif + +#ifdef RLIMIT_LOCKS +#ifndef RLIMIT_MEMLOCK +#error "Houston, we have a numbering problem!" +#endif + table[36].data = &c->rlimit_locks; +#endif + +#ifdef RLIMIT_SIGPENDING +#ifndef RLIMIT_LOCKS +#error "Houston, we have a numbering problem!" +#endif + table[37].data = &c->rlimit_sigpending; +#endif + +#ifdef RLIMIT_MSGQUEUE +#ifndef RLIMIT_SIGPENDING +#error "Houston, we have a numbering problem!" +#endif + table[38].data = &c->rlimit_msgqueue; +#endif + +#ifdef RLIMIT_NICE +#ifndef RLIMIT_MSGQUEUE +#error "Houston, we have a numbering problem!" +#endif + table[39].data = &c->rlimit_nice; +#endif + +#ifdef RLIMIT_RTPRIO +#ifndef RLIMIT_NICE +#error "Houston, we have a numbering problem!" +#endif + table[40].data = &c->rlimit_rtprio; #endif + +#ifdef RLIMIT_RTTIME +#ifndef RLIMIT_RTTIME +#error "Houston, we have a numbering problem!" +#endif + table[41].data = &c->rlimit_rttime; #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"); + pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file); if (!f && errno != ENOENT) { - pa_log("WARNING: failed to open configuration file '%s': %s", c->config_file, pa_cstrerror(errno)); + pa_log_warn("Failed to open configuration file: %s", 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; + pa_assert(c); if ((e = getenv(ENV_DL_SEARCH_PATH))) { pa_xfree(c->dl_search_path); @@ -339,6 +586,35 @@ int pa_daemon_conf_env(pa_daemon_conf *c) { return 0; } +const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c) { + pa_assert(c); + + if (!c->default_script_file) { + if (c->system_instance) + c->default_script_file = pa_find_config_file(DEFAULT_SYSTEM_SCRIPT_FILE, NULL, ENV_SCRIPT_FILE); + else + c->default_script_file = pa_find_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE); + } + + return c->default_script_file; +} + +FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c) { + FILE *f; + pa_assert(c); + + if (!c->default_script_file) { + if (c->system_instance) + f = pa_open_config_file(DEFAULT_SYSTEM_SCRIPT_FILE, NULL, ENV_SCRIPT_FILE, &c->default_script_file); + else + f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file); + } else + f = fopen(c->default_script_file, "r"); + + return f; +} + + static const char* const log_level_to_string[] = { [PA_LOG_DEBUG] = "debug", [PA_LOG_INFO] = "info", @@ -348,43 +624,76 @@ static const char* const log_level_to_string[] = { }; char *pa_daemon_conf_dump(pa_daemon_conf *c) { - pa_strbuf *s = pa_strbuf_new(); + 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); - assert(c->log_level <= PA_LOG_LEVEL_MAX); - - pa_strbuf_printf(s, "daemonize = %i\n", !!c->daemonize); - pa_strbuf_printf(s, "fail = %i\n", !!c->fail); - pa_strbuf_printf(s, "high-priority = %i\n", !!c->high_priority); - pa_strbuf_printf(s, "disallow-module-loading = %i\n", !!c->disallow_module_loading); + 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, "dl-search-path = %s\n", pa_strempty(c->dl_search_path)); + pa_strbuf_printf(s, "default-script-file = %s\n", pa_strempty(pa_daemon_conf_get_default_script_file(c))); + pa_strbuf_printf(s, "load-default-script-file = %s\n", pa_yes_no(c->load_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, "use-pid-file = %i\n", c->use_pid_file); - pa_strbuf_printf(s, "system-instance = %i\n", !!c->system_instance); - pa_strbuf_printf(s, "no-cpu-limit = %i\n", !!c->no_cpu_limit); - pa_strbuf_printf(s, "disable_shm = %i\n", !!c->disable_shm); + 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-data = %li\n", c->rlimit_data.is_set ? (long int) c->rlimit_data.value : -1); pa_strbuf_printf(s, "rlimit-stack = %li\n", c->rlimit_stack.is_set ? (long int) c->rlimit_stack.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-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1); + pa_strbuf_printf(s, "rlimit-rss = %li\n", c->rlimit_rss.is_set ? (long int) c->rlimit_rss.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 + pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1); #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_LOCKS + pa_strbuf_printf(s, "rlimit-locks = %li\n", c->rlimit_locks.is_set ? (long int) c->rlimit_locks.value : -1); +#endif +#ifdef RLIMIT_SIGPENDING + pa_strbuf_printf(s, "rlimit-sigpending = %li\n", c->rlimit_sigpending.is_set ? (long int) c->rlimit_sigpending.value : -1); +#endif +#ifdef RLIMIT_MSGQUEUE + pa_strbuf_printf(s, "rlimit-msgqueue = %li\n", c->rlimit_msgqueue.is_set ? (long int) c->rlimit_msgqueue.value : -1); +#endif +#ifdef RLIMIT_NICE + pa_strbuf_printf(s, "rlimit-nice = %li\n", c->rlimit_nice.is_set ? (long int) c->rlimit_nice.value : -1); #endif - +#ifdef RLIMIT_RTPRIO + pa_strbuf_printf(s, "rlimit-rtprio = %li\n", c->rlimit_rtprio.is_set ? (long int) c->rlimit_rtprio.value : -1); +#endif +#ifdef RLIMIT_RTTIME + pa_strbuf_printf(s, "rlimit-rttime = %li\n", c->rlimit_rttime.is_set ? (long int) c->rlimit_rttime.value : -1); +#endif +#endif + return pa_strbuf_tostring_free(s); } diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index b4b833ad..be2fe1ab 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -1,11 +1,12 @@ #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, @@ -23,6 +24,9 @@ ***/ #include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulse/sample.h> #ifdef HAVE_SYS_RESOURCE_H #include <sys/resource.h> @@ -31,52 +35,80 @@ /* The actual command to execute */ typedef enum pa_daemon_conf_cmd { PA_CMD_DAEMON, /* the default */ + PA_CMD_START, PA_CMD_HELP, PA_CMD_VERSION, PA_CMD_DUMP_CONF, PA_CMD_DUMP_MODULES, PA_CMD_KILL, - PA_CMD_CHECK + 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; - int is_set; + 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; - int daemonize, + pa_bool_t daemonize, fail, high_priority, + realtime_scheduling, disallow_module_loading, - exit_idle_time, - module_idle_time, - scache_idle_time, - auto_log_target, use_pid_file, system_instance, no_cpu_limit, - disable_shm; + disable_shm, + disable_remixing, + load_default_script_file; + 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; - int resample_method; char *config_file; - + #ifdef HAVE_SYS_RESOURCE_H - pa_rlimit rlimit_as, rlimit_core, rlimit_data, rlimit_fsize, rlimit_nofile, rlimit_stack; + pa_rlimit rlimit_fsize, rlimit_data, rlimit_stack, rlimit_core, rlimit_rss, rlimit_nofile, rlimit_as; #ifdef RLIMIT_NPROC pa_rlimit rlimit_nproc; #endif #ifdef RLIMIT_MEMLOCK pa_rlimit rlimit_memlock; #endif +#ifdef RLIMIT_LOCKS + pa_rlimit rlimit_locks; #endif - +#ifdef RLIMIT_SIGPENDING + pa_rlimit rlimit_sigpending; +#endif +#ifdef RLIMIT_MSGQUEUE + pa_rlimit rlimit_msgqueue; +#endif +#ifdef RLIMIT_NICE + pa_rlimit rlimit_nice; +#endif +#ifdef RLIMIT_RTPRIO + pa_rlimit rlimit_rtprio; +#endif +#ifdef RLIMIT_RTTIME + pa_rlimit rlimit_rttime; +#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 */ @@ -102,4 +134,7 @@ 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); +const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c); +FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c); + #endif diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in index 29b22a42..dfabcfb2 100644 --- a/src/daemon/daemon.conf.in +++ b/src/daemon/daemon.conf.in @@ -1,5 +1,3 @@ -# $Id$ -# # This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify @@ -17,85 +15,59 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. -## Configuration file for the pulseaudio daemon. Default values are -## commented out. Use either ; or # for commenting - -# Extra verbositiy -; verbose = 0 +## 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 after startup -; daemonize = 0 +; daemonize = no +; fail = yes +; disallow-module-loading = no +; use-pid-file = yes +; system-instance = no +; disable-shm = no -## Quit if startup fails -; fail = 1 +; high-priority = yes +; nice-level = -11 -## Renice the daemon to level -15 and try to get SCHED_FIFO -## scheduling. This a good idea if you hear annyoing noise in the -## playback. However, this is a certain security issue, since it works -## when called SUID root only. root is dropped immediately after gaining -## the nice level and SCHED_FIFO scheduling on startup. -; high-priority = 0 +; realtime-scheduling = no +; realtime-priority = 5 -## Disallow module loading after startup -; disallow-module-loading = 0 - -## Terminate the daemon after the last client quit and this time -## passed. Use a negative value to disable this feature. ; exit-idle-time = -1 - -## Unload autoloaded modules after being idle for this time ; module-idle-time = 20 - -## Unload autoloaded sample cache entries after being idle for this time ; scache-idle-time = 20 -## The path were to look for dynamic shared objects (DSOs aka -## plugins). You may specify more than one path seperated by -## colons. -; dl-search-path = @PA_DLSEARCHPATH@ +; dl-search-path = (depends on architecture) -## The default script file to load. Specify an empty string for not -## loading a default script file. The +; load-defaul-script-file = yes ; default-script-file = @PA_DEFAULT_CONFIG_FILE@ -## The default log target. Use either "stderr", "syslog" or -## "auto". The latter is equivalent to "sylog" in case daemonize is -## true, otherwise to "stderr". ; log-target = auto +; log-level = notice -## The resampling algorithm to use. Use one of src-sinc-best-quality, -## src-sinc-medium-quality, src-sinc-fastest, src-zero-order-hold, -## src-linear, trivial. See the documentation of libsamplerate for an -## explanation for the different methods. The method 'trivial' is the -## only algorithm implemented without usage of floating point -## numbers. If you're tight on CPU consider using this. On the other -## hand it has the worst quality of all. -; resample-method = sinc-fastest - -## Create a PID file in /tmp/pulseaudio-$USER/pid. Of this is enabled -## you may use commands like "pulseaudio --kill" or "pulseaudio -## --check". If you are planning to start more than one pulseaudio -## process per user, you better disable this option since it -## effectively disables multiple instances. -; use-pid-file = 1 - -## Do not install the CPU load limit, even on platforms where it is -## supported. This option is useful when debugging/profiling -## PulseAudio to disable disturbing SIGXCPU signals. -; no-cpu-limit = 0 +; resample-method = speex-float-3 +; disable-remixing = no -## Run the daemon as system-wide instance, requires root priviliges -; system-instance = 0 +; no-cpu-limit = no -## Resource limits, see getrlimit(2) for more information -; rlimit-as = -1 -; rlimit-core = -1 -; rlimit-data = -1 ; rlimit-fsize = -1 -; rlimit-nofile = 200 +; rlimit-data = -1 ; rlimit-stack = -1 +; rlimit-core = -1 +; rlimit-as = -1 +; rlimit-rss = -1 ; rlimit-nproc = -1 -; rlimit-memlock = 25 - -## Disable shared memory data transfer -; disable-shm = 0 +; rlimit-nofile = 256 +; rlimit-memlock = -1 +; rlimit-locks = -1 +; rlimit-sigpending = -1 +; rlimit-msgqueue = -1 +; rlimit-nice = 31 +; rlimit-rtprio = 9 +; rlimit-rtttime = 1000000 + +; 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 index af2a6789..aad9f5d6 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -1,5 +1,4 @@ -#!@PA_BINARY@ -nF - +#!@PA_BINARY@ -nF # # This file is part of PulseAudio. # @@ -17,8 +16,19 @@ # 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 +### 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 @@ -27,22 +37,18 @@ #load-module module-pipe-sink ### Automatically load driver modules depending on the hardware available -@HAVE_HAL_TRUE@load-module module-hal-detect - +.ifexists module-hal-detect@PA_SOEXT@ +load-module module-hal-detect +.else ### Alternatively use the static hardware detection module (for systems that -### lack HAL support -@HAVE_HAL_FALSE@load-module module-detect - -### Load audio drivers automatically on access -#add-autoload-sink output module-oss device="/dev/dsp" sink_name=output source_name=input -#add-autoload-source input module-oss device="/dev/dsp" sink_name=output source_name=input -#add-autoload-sink output module-oss-mmap device="/dev/dsp" sink_name=output source_name=input -#add-autoload-source input module-oss-mmap device="/dev/dsp" sink_name=output source_name=input -#add-autoload-sink output module-alsa-sink sink_name=output -#add-autoload-source input module-alsa-source source_name=input +### lack HAL support) +load-module module-detect +.endif ### Load several protocols +.ifexists module-esound-protocol-unix@PA_SOEXT@ load-module module-esound-protocol-unix +.endif load-module module-native-protocol-unix ### Network access (may be configured with paprefs, so leave this commented @@ -61,27 +67,50 @@ load-module module-native-protocol-unix ### 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 -### Make some devices default -#set-default-sink output -#set-default-source input +### Make sure we always have a sink around, even if it is a null sink. +load-module module-always-sink -.nofail - -### Load something to the sample cache -load-sample x11-bell /usr/share/sounds/gtk-events/activate.wav -#load-sample-dir-lazy /usr/share/sounds/*.wav +### 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 +#load-module module-x11-bell sample=bell-windowing-system -### Publish connection data in the X11 root window -load-module module-x11-publish +### 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 + +### If autoexit on idle is enabled we want to make sure we only quit +### when no local session needs us anymore. +load-module module-console-kit + +### Enable positioned event sounds +load-module module-position-event-sounds ### 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 module-gconf@PA_SOEXT@ +.nofail load-module module-gconf +.fail +.endif + +### Publish connection data in the X11 root window +.ifexists module-x11-publish@PA_SOEXT@ +.nofail +load-module module-x11-publish +.fail +.endif + +### Make some devices default +#set-default-sink output +#set-default-source input diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c index 06734ea6..cd6866aa 100644 --- a/src/daemon/dumpmodules.c +++ b/src/daemon/dumpmodules.c @@ -1,8 +1,9 @@ -/* $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, @@ -25,34 +26,38 @@ #include <string.h> #include <getopt.h> -#include <assert.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) { - assert(name && 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; - assert(name && i); - + 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 { @@ -64,15 +69,18 @@ static void long_info(const char *name, const char *path, pa_modinfo *i) { 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); @@ -86,11 +94,11 @@ static int is_preloaded(const char *name) { for (l = lt_preloaded_symbols; l->name; l++) { char buf[64], *e; - + if (l->address) continue; - - snprintf(buf, sizeof(buf), "%s", l->name); + + pa_snprintf(buf, sizeof(buf), "%s", l->name); if ((e = strrchr(buf, '.'))) *e = 0; @@ -112,12 +120,14 @@ static int callback(const char *path, lt_ptr data) { 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++) @@ -127,20 +137,20 @@ void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]) { 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; - - snprintf(buf, sizeof(buf), "%s", l->name); + + 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 index 05cd86e0..c49a5eda 100644 --- a/src/daemon/dumpmodules.h +++ b/src/daemon/dumpmodules.h @@ -1,11 +1,11 @@ #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, diff --git a/src/daemon/esdcompat.in b/src/daemon/esdcompat.in index 942389d2..66501803 100755 --- a/src/daemon/esdcompat.in +++ b/src/daemon/esdcompat.in @@ -1,7 +1,5 @@ #!/bin/sh -# $Id$ -# # This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify diff --git a/src/daemon/ltdl-bind-now.c b/src/daemon/ltdl-bind-now.c new file mode 100644 index 00000000..b1770674 --- /dev/null +++ b/src/daemon/ltdl-bind-now.c @@ -0,0 +1,195 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write 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..f95d13b4 --- /dev/null +++ b/src/daemon/ltdl-bind-now.h @@ -0,0 +1,30 @@ +#ifndef foopulsecoreltdlbindnowhfoo +#define foopulsecoreltdlbindnowhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General 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 index 5d77282c..14594416 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -1,18 +1,19 @@ -/* $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 @@ -30,7 +31,6 @@ #include <stdio.h> #include <signal.h> #include <stddef.h> -#include <assert.h> #include <ltdl.h> #include <limits.h> #include <fcntl.h> @@ -56,13 +56,16 @@ #include <tcpd.h> #endif -#include "../pulsecore/winsock.h" +#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> @@ -75,12 +78,23 @@ #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" + +#define AUTOSPAWN_LOCK "autospawn.lock" #ifdef HAVE_LIBWRAP /* Only one instance of these variables */ @@ -101,7 +115,7 @@ static void message_cb(pa_mainloop_api*a, pa_time_event*e, PA_GCC_UNUSED const s MSG msg; struct timeval tvnext; - while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) raise(SIGTERM); else { @@ -117,7 +131,7 @@ static void message_cb(pa_mainloop_api*a, pa_time_event*e, PA_GCC_UNUSED const s #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_strsignal(sig)); + pa_log_info("Got signal %s.", pa_sig2str(sig)); switch (sig) { #ifdef SIGUSR1 @@ -125,7 +139,7 @@ static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e, pa_module_load(userdata, "module-cli", NULL); break; #endif - + #ifdef SIGUSR2 case SIGUSR2: pa_module_load(userdata, "module-cli-protocol-unix", NULL); @@ -150,16 +164,6 @@ static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e, } } -static void close_pipe(int p[2]) { - if (p[0] != -1) - close(p[0]); - if (p[1] != -1) - close(p[1]); - p[0] = p[1] = -1; -} - -#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) { @@ -170,7 +174,7 @@ static int change_user(void) { /* 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; @@ -197,7 +201,14 @@ static int change_user(void) { pa_log("Failed to create '%s': %s", PA_SYSTEM_RUNTIME_PATH, pa_cstrerror(errno)); return -1; } - + + if (pa_make_secure_dir(PA_SYSTEM_STATE_PATH, 0700, pw->pw_uid, gr->gr_gid) < 0) { + pa_log("Failed to create '%s': %s", PA_SYSTEM_STATE_PATH, pa_cstrerror(errno)); + return -1; + } + + /* We don't create the config dir here, because we don't need to write to it */ + if (initgroups(PA_SYSTEM_USER, gr->gr_gid) != 0) { pa_log("Failed to change group list: %s", pa_cstrerror(errno)); return -1; @@ -235,13 +246,15 @@ static int change_user(void) { return -1; } - set_env("USER", PA_SYSTEM_USER); - set_env("LOGNAME", PA_SYSTEM_GROUP); - set_env("HOME", PA_SYSTEM_RUNTIME_PATH); + pa_set_env("USER", PA_SYSTEM_USER); + pa_set_env("USERNAME", PA_SYSTEM_USER); + pa_set_env("LOGNAME", PA_SYSTEM_USER); + pa_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_set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH); + pa_set_env("PULSE_CONFIG_PATH", PA_SYSTEM_CONFIG_PATH); + pa_set_env("PULSE_STATE_PATH", PA_SYSTEM_STATE_PATH); pa_log_info("Successfully dropped root privileges."); @@ -257,51 +270,57 @@ static int change_user(void) { #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 void set_one_rlimit(const pa_rlimit *r, int resource, const char *name) { +static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) { struct rlimit rl; - assert(r); + pa_assert(r); if (!r->is_set) - return; + 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)); + if (setrlimit(resource, &rl) < 0) { + pa_log_info("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_data, RLIMIT_DATA, "RLIMIT_DATA"); set_one_rlimit(&conf->rlimit_stack, RLIMIT_STACK, "RLIMIT_STACK"); + set_one_rlimit(&conf->rlimit_core, RLIMIT_CORE, "RLIMIT_CORE"); + set_one_rlimit(&conf->rlimit_rss, RLIMIT_RSS, "RLIMIT_RSS"); #ifdef RLIMIT_NPROC set_one_rlimit(&conf->rlimit_nproc, RLIMIT_NPROC, "RLIMIT_NPROC"); #endif + set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE"); #ifdef RLIMIT_MEMLOCK set_one_rlimit(&conf->rlimit_memlock, RLIMIT_MEMLOCK, "RLIMIT_MEMLOCK"); #endif + set_one_rlimit(&conf->rlimit_as, RLIMIT_AS, "RLIMIT_AS"); +#ifdef RLIMIT_LOCKS + set_one_rlimit(&conf->rlimit_locks, RLIMIT_LOCKS, "RLIMIT_LOCKS"); +#endif +#ifdef RLIMIT_SIGPENDING + set_one_rlimit(&conf->rlimit_sigpending, RLIMIT_SIGPENDING, "RLIMIT_SIGPENDING"); +#endif +#ifdef RLIMIT_MSGQUEUE + set_one_rlimit(&conf->rlimit_msgqueue, RLIMIT_MSGQUEUE, "RLIMIT_MSGQUEUE"); +#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 +#ifdef RLIMIT_RTTIME + set_one_rlimit(&conf->rlimit_rttime, RLIMIT_RTTIME, "RLIMIT_RTTIME"); +#endif } #endif @@ -310,57 +329,89 @@ int main(int argc, char *argv[]) { pa_strbuf *buf = NULL; pa_daemon_conf *conf = NULL; pa_mainloop *mainloop = NULL; - - char *s; - int r, retval = 1, d = 0; - int daemon_pipe[2] = { -1, -1 }; - int suid_root, real_root; - int valid_pid_file = 0; - -#ifdef HAVE_GETUID + char *s; + int r = 0, retval = 1, d = 0; + pa_bool_t suid_root, real_root; + pa_bool_t valid_pid_file = FALSE; gid_t gid = (gid_t) -1; + pa_bool_t ltdl_init = FALSE; + int passed_fd = -1; + const char *e; +#ifdef HAVE_FORK + int daemon_pipe[2] = { -1, -1 }; #endif - #ifdef OS_IS_WIN32 - pa_time_event *timer; - struct timeval tv; + pa_time_event *win32_timer; + struct timeval win32_tv; #endif + char *lf = NULL; + int autospawn_lock_fd = -1; - setlocale(LC_ALL, ""); +#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; - pa_limit_caps(); + /* We have to execute ourselves, because the libc caches the + * value of $LD_BIND_NOW on initialization. */ + + pa_set_env("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; - - if (suid_root && (pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) <= 0 || gid >= 1000)) { - pa_log_warn("WARNING: called SUID root, but not in group '"PA_REALTIME_GROUP"'."); - pa_drop_root(); - } #else - real_root = 0; - suid_root = 0; + real_root = FALSE; + suid_root = FALSE; #endif - - LTDL_SET_PRELOADED_SYMBOLS(); - - r = lt_dlinit(); - assert(r == 0); -#ifdef OS_IS_WIN32 - { - WSADATA data; - WSAStartup(MAKEWORD(2, 0), &data); + if (!real_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. */ } -#endif - pa_random_seed(); - + if ((e = getenv("PULSE_PASSED_FD"))) { + passed_fd = atoi(e); + + if (passed_fd <= 2) + passed_fd = -1; + } + + pa_close_all(passed_fd, -1); + + pa_reset_sigs(-1); + pa_unblock_sigs(-1); + + /* 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; @@ -368,24 +419,140 @@ int main(int argc, char *argv[]) { goto finish; if (pa_cmdline_parse(conf, argc, argv, &d) < 0) { - pa_log("failed to parse command line."); + 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 (conf->high_priority && conf->cmd == PA_CMD_DAEMON) - pa_raise_priority(); + pa_log_debug("Started as real root: %s, suid root: %s", pa_yes_no(real_root), pa_yes_no(suid_root)); - pa_drop_caps(); + if (!real_root && pa_have_caps()) { + pa_bool_t allow_high_priority = FALSE, allow_realtime = FALSE; - if (suid_root) - pa_drop_root(); + /* Let's better not enable high prio or RT by default */ + + if (conf->high_priority && !allow_high_priority) { + if (pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) { + pa_log_info("We're in the group '"PA_REALTIME_GROUP"', allowing high-priority scheduling."); + allow_high_priority = TRUE; + } + } + + if (conf->realtime_scheduling && !allow_realtime) { + if (pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) { + pa_log_info("We're in the group '"PA_REALTIME_GROUP"', allowing real-time scheduling."); + allow_realtime = TRUE; + } + } + +#ifdef HAVE_POLKIT + if (conf->high_priority && !allow_high_priority) { + if (pa_polkit_check("org.pulseaudio.acquire-high-priority") > 0) { + pa_log_info("PolicyKit grants us acquire-high-priority privilege."); + allow_high_priority = TRUE; + } else + pa_log_info("PolicyKit refuses acquire-high-priority privilege."); + } + + if (conf->realtime_scheduling && !allow_realtime) { + if (pa_polkit_check("org.pulseaudio.acquire-real-time") > 0) { + pa_log_info("PolicyKit grants us acquire-real-time privilege."); + allow_realtime = TRUE; + } else + pa_log_info("PolicyKit refuses acquire-real-time privilege."); + } +#endif + + 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(); + + 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."); + } + } + +#ifdef HAVE_SYS_RESOURCE_H + /* Reset resource limits. If we are run as root (for system mode) + * this might end up increasing the limits, which is intended + * behaviour. For all other cases, i.e. started as normal user, or + * SUID root at this point we should have no CAP_SYS_RESOURCE and + * increasing the limits thus should fail. Which is, too, intended + * behaviour */ + + set_all_rlimits(conf); +#endif + + if (conf->high_priority && !pa_can_high_priority()) + pa_log_warn("High-priority scheduling enabled in configuration but not allowed by policy."); + + if (conf->high_priority && (conf->cmd == PA_CMD_DAEMON || conf->cmd == PA_CMD_START)) + pa_raise_priority(conf->nice_level); + + if (pa_have_caps()) { + pa_bool_t drop; + + drop = (conf->cmd != PA_CMD_DAEMON && conf->cmd != PA_CMD_START) || !conf->realtime_scheduling; + +#ifdef RLIMIT_RTPRIO + if (!drop) { + struct rlimit rl; + /* 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. */ + + if (getrlimit(RLIMIT_RTPRIO, &rl) >= 0) { + + if (rl.rlim_cur >= 9) + drop = TRUE; + else { + rl.rlim_max = rl.rlim_cur = 9; + + if (setrlimit(RLIMIT_RTPRIO, &rl) >= 0) { + pa_log_info("Successfully increased RLIMIT_RTPRIO"); + drop = TRUE; + } else + pa_log_warn("RLIMIT_RTPRIO failed: %s", pa_cstrerror(errno)); + } + } + } +#endif + + if (drop) { + pa_log_info("Giving up CAP_NICE"); + pa_drop_caps(); + suid_root = FALSE; + } + } + + if (conf->realtime_scheduling && !pa_can_realtime()) + pa_log_warn("Real-time scheduling enabled in configuration but not allowed by policy."); + + pa_log_debug("Can realtime: %s, can high-priority: %s", pa_yes_no(pa_can_realtime()), pa_yes_no(pa_can_high_priority())); + + 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); @@ -400,6 +567,16 @@ int main(int argc, char *argv[]) { 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; @@ -413,10 +590,10 @@ int main(int argc, char *argv[]) { case PA_CMD_CHECK: { pid_t pid; - if (pa_pid_file_check_running(&pid) < 0) { - pa_log_info("daemon not running"); - } else { - pa_log_info("daemon running as PID %u", 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; } @@ -425,64 +602,92 @@ int main(int argc, char *argv[]) { } case PA_CMD_KILL: - if (pa_pid_file_kill(SIGINT, NULL) < 0) - pa_log("failed to kill daemon."); + 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: - assert(conf->cmd == PA_CMD_DAEMON); + pa_assert(conf->cmd == PA_CMD_DAEMON || conf->cmd == PA_CMD_START); } - if (real_root && !conf->system_instance) { + 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) { + else if (!real_root && conf->system_instance) { pa_log("Root priviliges required."); goto finish; } + if (conf->cmd == PA_CMD_START) { + /* If we shall start PA only when it is not running yet, we + * first take the autospawn lock to make things + * synchronous. */ + + lf = pa_runtime_path(AUTOSPAWN_LOCK); + autospawn_lock_fd = pa_lock_lockfile(lf); + } + if (conf->daemonize) { pid_t child; int tty_fd; if (pa_stdio_acquire() < 0) { - pa_log("failed to acquire stdio."); + pa_log("Failed to acquire stdio."); goto finish; } #ifdef HAVE_FORK if (pipe(daemon_pipe) < 0) { - pa_log("failed to create pipe."); + pa_log("pipe failed: %s", pa_cstrerror(errno)); goto finish; } - + if ((child = fork()) < 0) { pa_log("fork() failed: %s", pa_cstrerror(errno)); goto finish; } if (child != 0) { + ssize_t n; /* Father */ - close(daemon_pipe[1]); + 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)); + if ((n = pa_loop_read(daemon_pipe[0], &retval, sizeof(retval), NULL)) != sizeof(retval)) { + + if (n < 0) + pa_log("read() failed: %s", pa_cstrerror(errno)); + retval = 1; } if (retval) - pa_log("daemon startup failed."); + pa_log("Daemon startup failed."); else - pa_log_info("daemon startup successful."); - + pa_log_info("Daemon startup successful."); + goto finish; } - close(daemon_pipe[0]); + if (autospawn_lock_fd >= 0) { + /* The lock file is unlocked from the parent, so we need + * to close it in the child */ + + pa_close(autospawn_lock_fd); + autospawn_lock_fd = -1; + } + + pa_assert_se(pa_close(daemon_pipe[0]) == 0); daemon_pipe[0] = -1; #endif @@ -497,13 +702,13 @@ int main(int argc, char *argv[]) { #endif #ifndef OS_IS_WIN32 - close(0); - close(1); - close(2); + pa_close(0); + pa_close(1); + pa_close(2); - open("/dev/null", O_RDONLY); - open("/dev/null", O_WRONLY); - open("/dev/null", O_WRONLY); + pa_assert_se(open("/dev/null", O_RDONLY) == 0); + pa_assert_se(open("/dev/null", O_WRONLY) == 1); + pa_assert_se(open("/dev/null", O_WRONLY) == 2); #else FreeConsole(); #endif @@ -517,60 +722,95 @@ int main(int argc, char *argv[]) { #ifdef SIGTSTP signal(SIGTSTP, SIG_IGN); #endif - + #ifdef TIOCNOTTY if ((tty_fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(tty_fd, TIOCNOTTY, (char*) 0); - close(tty_fd); + pa_assert_se(pa_close(tty_fd) == 0); } #endif } - chdir("/"); + pa_set_env("PULSE_INTERNAL", "1"); + pa_assert_se(chdir("/") == 0); umask(0022); - - if (conf->system_instance) { + + if (conf->system_instance) if (change_user() < 0) goto finish; - } else if (create_runtime_dir() < 0) + + pa_set_env("PULSE_SYSTEM", conf->system_instance ? "1" : "0"); + + pa_log_info("This is PulseAudio " PACKAGE_VERSION); + pa_log_info("Page size is %lu bytes", (unsigned long) PA_PAGE_SIZE); + if (!(s = pa_get_runtime_dir())) goto finish; - + pa_log_info("Using runtime directory %s.", s); + pa_xfree(s); + if (!(s = pa_get_state_dir())) + pa_log_info("Using state directory %s.", s); + pa_xfree(s); + + pa_log_info("Running in system mode: %s", pa_yes_no(pa_in_system_mode())); + if (conf->use_pid_file) { - if (pa_pid_file_create() < 0) { + int z; + + if ((z = pa_pid_file_create("pulseaudio")) != 0) { + + if (conf->cmd == PA_CMD_START && z > 0) { + /* If we are already running and with are run in + * --start mode, then let's return this as success. */ + + pa_log_info("z=%i rock!", z); + + retval = 0; + goto finish; + } + 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; + valid_pid_file = TRUE; } -#ifdef HAVE_SYS_RESOURCE_H - set_all_rlimits(conf); -#endif - #ifdef SIGPIPE signal(SIGPIPE, SIG_IGN); #endif - mainloop = pa_mainloop_new(); - assert(mainloop); + 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."); + pa_log("pa_core_new() failed."); goto finish; } - c->is_system_instance = !!conf->system_instance; - - r = pa_signal_init(pa_mainloop_get_api(mainloop)); - assert(r == 0); + 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; + c->running_as_daemon = !!conf->daemonize; + + 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 @@ -580,84 +820,97 @@ int main(int argc, char *argv[]) { #ifdef SIGHUP pa_signal_new(SIGHUP, signal_callback, c); #endif - + #ifdef OS_IS_WIN32 - timer = pa_mainloop_get_api(mainloop)->time_new( - pa_mainloop_get_api(mainloop), pa_gettimeofday(&tv), message_cb, NULL); - assert(timer); + win32_timer = pa_mainloop_get_api(mainloop)->time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&win32_tv), message_cb, NULL); #endif - if (conf->daemonize) - c->running_as_daemon = 1; - oil_init(); - if (!conf->no_cpu_limit) { - r = pa_cpu_limit_init(pa_mainloop_get_api(mainloop)); - assert(r == 0); - } - + 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 (conf->load_default_script_file) { + FILE *f; + + if ((f = pa_daemon_conf_open_default_script_file(conf))) { + r = pa_cli_command_execute_file_stream(c, f, buf, &conf->fail); + fclose(f); + } + } 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 { + pa_log("Failed to initialize daemon."); + goto finish; + } + + if (!c->modules || pa_idxset_size(c->modules) == 0) { + pa_log("Daemon startup without any loaded modules, refusing to work."); + goto finish; + } + + if (c->default_sink_name && !pa_namereg_get(c, c->default_sink_name, PA_NAMEREG_SINK, TRUE) && conf->fail) { + pa_log_error("Default sink name (%s) does not exist in name register.", c->default_sink_name); + goto finish; + } - retval = 0; #ifdef HAVE_FORK - if (conf->daemonize) - pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); -#endif - - c->disallow_module_loading = conf->disallow_module_loading; - 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; - - 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."); - } + if (daemon_pipe[1] >= 0) { + int ok = 0; + pa_loop_write(daemon_pipe[1], &ok, sizeof(ok), NULL); + pa_close(daemon_pipe[1]); + daemon_pipe[1] = -1; } +#endif + + pa_log_info("Daemon startup complete."); + + retval = 0; + if (pa_mainloop_run(mainloop, &retval) < 0) + goto finish; + + pa_log_info("Daemon shutdown initiated."); + +finish: + + if (autospawn_lock_fd >= 0) + pa_unlock_lockfile(lf, autospawn_lock_fd); + + if (lf) + pa_xfree(lf); #ifdef OS_IS_WIN32 - pa_mainloop_get_api(mainloop)->time_free(timer); + if (win32_timer) + pa_mainloop_get_api(mainloop)->time_free(win32_timer); #endif - pa_core_free(c); + if (c) { + pa_core_unref(c); + pa_log_info("Daemon terminated."); + } if (!conf->no_cpu_limit) pa_cpu_limit_done(); - + pa_signal_done(); - - pa_log_info("Daemon terminated."); - -finish: + +#ifdef HAVE_FORK + if (daemon_pipe[1] >= 0) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); + + pa_close_pipe(daemon_pipe); +#endif if (mainloop) pa_mainloop_free(mainloop); @@ -667,14 +920,17 @@ finish: if (valid_pid_file) pa_pid_file_remove(); - - close_pipe(daemon_pipe); #ifdef OS_IS_WIN32 WSACleanup(); #endif - lt_dlexit(); - + if (ltdl_init) + pa_ltdl_done(); + +#ifdef HAVE_DBUS + dbus_shutdown(); +#endif + return retval; } diff --git a/src/daemon/org.pulseaudio.policy b/src/daemon/org.pulseaudio.policy new file mode 100644 index 00000000..6cdeec68 --- /dev/null +++ b/src/daemon/org.pulseaudio.policy @@ -0,0 +1,50 @@ +<?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"> + +<!-- +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> + <vendor>The PulseAudio Project</vendor> + <vendor_url>http://pulseaudio.org/</vendor_url> + <icon_name>audio-card</icon_name> + + <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/polkit.c b/src/daemon/polkit.c new file mode 100644 index 00000000..256e3199 --- /dev/null +++ b/src/daemon/polkit.c @@ -0,0 +1,165 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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" + +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 (;;) { + + 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; + } + + 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 || + polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT || + 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 || + polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT + ) { + + if (polkit_auth_obtain(action_id, 0, getpid(), &dbus_error)) { + polkit_result = POLKIT_RESULT_YES; + break; + } + + if (dbus_error_is_set(&dbus_error)) { + pa_log_error("Cannot obtain auth: %s", dbus_error.message); + goto finish; + } + } + + 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/pulsecore/core-def.h b/src/daemon/polkit.h index 718499d1..0d65ec52 100644 --- a/src/pulsecore/core-def.h +++ b/src/daemon/polkit.h @@ -1,30 +1,27 @@ -#ifndef foocoredefhfoo -#define foocoredefhfoo - -/* $Id$ */ +#ifndef foopolkithfoo +#define foopolkithfoo /*** 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. ***/ -typedef enum pa_mixer { - PA_MIXER_SOFTWARE, - PA_MIXER_HARDWARE -} pa_mixer_t; +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 index a20bc7c0..6cb3cb21 100755 --- a/src/depmod.py +++ b/src/depmod.py @@ -1,6 +1,5 @@ #!/usr/bin/python -# $Id$ -# + # This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify diff --git a/src/map-file b/src/map-file new file mode 100644 index 00000000..8d1c582e --- /dev/null +++ b/src/map-file @@ -0,0 +1,256 @@ +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_extend; +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_index; +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_new_with_proplist; +pa_context_play_sample; +pa_context_play_sample_with_proplist; +pa_context_proplist_remove; +pa_context_proplist_update; +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_contains; +pa_proplist_clear; +pa_proplist_copy; +pa_proplist_get; +pa_proplist_gets; +pa_proplist_iterate; +pa_proplist_update; +pa_proplist_new; +pa_proplist_set; +pa_proplist_sets; +pa_proplist_setf; +pa_proplist_unset; +pa_proplist_unset_many; +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_monitor_stream; +pa_stream_get_sample_spec; +pa_stream_get_state; +pa_stream_get_time; +pa_stream_get_timing_info; +pa_stream_is_corked; +pa_stream_is_suspended; +pa_stream_new; +pa_stream_new_with_proplist; +pa_stream_peek; +pa_stream_prebuf; +pa_stream_proplist_remove; +pa_stream_proplist_update; +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_monitor_stream; +pa_stream_set_name; +pa_stream_set_overflow_callback; +pa_stream_set_read_callback; +pa_stream_set_started_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_timeval_sub; +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/.gitignore b/src/modules/.gitignore new file mode 100644 index 00000000..2d2d942d --- /dev/null +++ b/src/modules/.gitignore @@ -0,0 +1 @@ +module-*-symdef.h diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c index d8b6c5cc..5d52cbc9 100644 --- a/src/modules/alsa-util.c +++ b/src/modules/alsa-util.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,12 +25,16 @@ #endif #include <sys/types.h> +#include <limits.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 <pulsecore/atomic.h> #include "alsa-util.h" @@ -39,7 +44,6 @@ struct pa_alsa_fdlist { /* This is a temporary buffer used to avoid lots of mallocs */ struct pollfd *work_fds; - snd_pcm_t *pcm; snd_mixer_t *mixer; pa_mainloop_api *m; @@ -53,11 +57,16 @@ struct pa_alsa_fdlist { }; 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 = (struct pa_alsa_fdlist*)userdata; + + struct pa_alsa_fdlist *fdl = userdata; int err, i; unsigned short revents; - assert(a && fdl && (fdl->pcm || fdl->mixer) && fdl->fds && fdl->work_fds); + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + pa_assert(fdl->fds); + pa_assert(fdl->work_fds); if (fdl->polled) return; @@ -66,7 +75,7 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); - for (i = 0;i < fdl->num_fds;i++) { + 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; @@ -80,63 +89,46 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io } } - assert(i != fdl->num_fds); - - if (fdl->pcm) - err = snd_pcm_poll_descriptors_revents(fdl->pcm, fdl->work_fds, fdl->num_fds, &revents); - else - err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents); + pa_assert(i != fdl->num_fds); - if (err < 0) { - pa_log_error("Unable to get poll revent: %s", - snd_strerror(err)); + 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) { - if (fdl->pcm) - fdl->cb(fdl->userdata); - else - snd_mixer_handle_events(fdl->mixer); - } + 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 = (struct pa_alsa_fdlist*)userdata; + struct pa_alsa_fdlist *fdl = userdata; int num_fds, i, err; struct pollfd *temp; - assert(a && fdl && (fdl->pcm || fdl->mixer)); + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); a->defer_enable(fdl->defer, 0); - if (fdl->pcm) - num_fds = snd_pcm_poll_descriptors_count(fdl->pcm); - else - num_fds = snd_mixer_poll_descriptors_count(fdl->mixer); - assert(num_fds > 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_xmalloc0(sizeof(struct pollfd) * num_fds); - fdl->work_fds = pa_xmalloc(sizeof(struct pollfd) * num_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 (fdl->pcm) - err = snd_pcm_poll_descriptors(fdl->pcm, fdl->work_fds, num_fds); - else - err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds); - - if (err < 0) { - pa_log_error("Unable to get poll descriptors: %s", - snd_strerror(err)); + 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; } @@ -146,18 +138,18 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u return; if (fdl->ios) { - for (i = 0;i < fdl->num_fds;i++) + 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 = pa_xmalloc(sizeof(pa_io_event*) * num_fds); - assert(fdl->ios); + fdl->ios = NULL; } - } else { - fdl->ios = pa_xmalloc(sizeof(pa_io_event*) * num_fds); - assert(fdl->ios); } + if (!fdl->ios) + fdl->ios = pa_xnew(pa_io_event*, num_fds); + /* Swap pointers */ temp = fdl->work_fds; fdl->work_fds = fdl->fds; @@ -165,47 +157,41 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u fdl->num_fds = num_fds; - for (i = 0;i < num_fds;i++) { + 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); - assert(fdl->ios[i]); - } } struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { struct pa_alsa_fdlist *fdl; - fdl = pa_xmalloc(sizeof(struct pa_alsa_fdlist)); + fdl = pa_xnew0(struct pa_alsa_fdlist, 1); fdl->num_fds = 0; fdl->fds = NULL; fdl->work_fds = NULL; - - fdl->pcm = 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) { - assert(fdl); + pa_assert(fdl); if (fdl->defer) { - assert(fdl->m); + pa_assert(fdl->m); fdl->m->defer_free(fdl->defer); } if (fdl->ios) { int i; - assert(fdl->m); + pa_assert(fdl->m); for (i = 0;i < fdl->num_fds;i++) fdl->m->io_free(fdl->ios[i]); pa_xfree(fdl->ios); @@ -219,29 +205,15 @@ void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { pa_xfree(fdl); } -int pa_alsa_fdlist_init_pcm(struct pa_alsa_fdlist *fdl, snd_pcm_t *pcm_handle, pa_mainloop_api* m, void (*cb)(void *userdata), void *userdata) { - assert(fdl && pcm_handle && m && !fdl->m && cb); - - fdl->pcm = pcm_handle; - fdl->m = m; - - fdl->defer = m->defer_new(m, defer_cb, fdl); - assert(fdl->defer); - - fdl->cb = cb; - fdl->userdata = userdata; - - return 0; -} - -int pa_alsa_fdlist_init_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { - assert(fdl && mixer_handle && m && !fdl->m); +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); - assert(fdl->defer); return 0; } @@ -256,23 +228,27 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s [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_S16NE, - PA_SAMPLE_S16RE, PA_SAMPLE_FLOAT32NE, PA_SAMPLE_FLOAT32RE, - PA_SAMPLE_ULAW, + 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; - - assert(pcm_handle); - assert(f); + + pa_assert(pcm_handle); + pa_assert(f); if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; @@ -285,17 +261,21 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s *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; } @@ -305,89 +285,404 @@ try_auto: /* 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) { +int pa_alsa_set_hw_params( + snd_pcm_t *pcm_handle, + pa_sample_spec *ss, + uint32_t *periods, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched, + pa_bool_t require_exact_channel_number) { + int ret = -1; + snd_pcm_uframes_t _period_size = *period_size; + unsigned int _periods = *periods; 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; - - assert(pcm_handle); - assert(ss); - assert(periods); - assert(period_size); - - buffer_size = *periods * *period_size; - - if ((ret = snd_pcm_hw_params_malloc(&hwparams)) < 0 || - (ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0 || - (ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0 || - (ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) - goto finish; + pa_bool_t _use_mmap = use_mmap && *use_mmap; + pa_bool_t _use_tsched = use_tsched && *use_tsched; + int dir; - if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) + pa_assert(pcm_handle); + pa_assert(ss); + pa_assert(periods); + pa_assert(period_size); + + snd_pcm_hw_params_alloca(&hwparams); + + if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) + if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) + 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 ((*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)) + if (!_use_mmap) + _use_tsched = FALSE; + + if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) goto finish; - if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) + if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) goto finish; - if (ss->rate != r) { - pa_log_warn("device doesn't support %u Hz, changed to %u Hz.", ss->rate, r); + /* Adjust the buffer sizes, if we didn't get the rate we were asking for */ + _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * r) / ss->rate); + tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate); - /* If the sample rate deviates too much, we need to resample */ - if (r < ss->rate*.95 || r > ss->rate*1.05) - ss->rate = r; + 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 (ss->channels != c) { - pa_log_warn("device doesn't support %u channels, changed to %u.", ss->channels, c); - ss->channels = c; + if (_use_tsched) { + _period_size = tsched_size; + _periods = 1; + + pa_assert_se(snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size) == 0); + pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r); } - if (ss->format != f) { - pa_log_warn("device doesn't support sample format %s, changed to %s.", pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f)); - ss->format = f; + buffer_size = _periods * _period_size; + + if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) + goto finish; + + if (_periods > 0) { + dir = 1; + if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { + dir = -1; + if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) + goto finish; + } } - + + if (_period_size > 0) + if ((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) + if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 || + (ret = snd_pcm_hw_params_get_periods(hwparams, &_periods, &dir)) < 0) goto finish; - - assert(buffer_size > 0); - assert(*period_size > 0); - *periods = buffer_size / *period_size; - assert(*periods > 0); - + + /* 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(_periods > 0); + pa_assert(_period_size > 0); + + *periods = _periods; + *period_size = _period_size; + + if (use_mmap) + *use_mmap = _use_mmap; + + if (use_tsched) + *use_tsched = _use_tsched; + ret = 0; - + finish: - if (hwparams) - snd_pcm_hw_params_free(hwparams); - + return ret; } +int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { + 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_set_avail_min(pcm, swparams, avail_min)) < 0) { + pa_log_error("snd_pcm_sw_params_set_avail_min() failed: %s", 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, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched) { + + 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| + SND_PCM_NO_AUTO_RESAMPLE| + SND_PCM_NO_AUTO_CHANNELS| + SND_PCM_NO_AUTO_FORMAT)) < 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, tsched_size, use_mmap, use_tsched, 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 plughw: stuff */ + + d = pa_sprintf_malloc("plughw:%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, tsched_size, use_mmap, use_tsched); + 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, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched) { + + 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| + SND_PCM_NO_AUTO_RESAMPLE| + SND_PCM_NO_AUTO_CHANNELS| + SND_PCM_NO_AUTO_FORMAT)) < 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, tsched_size, use_mmap, use_tsched, 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_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA); + + return pcm_handle; + } +} + int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { int err; - assert(mixer && dev); + pa_assert(mixer); + pa_assert(dev); if ((err = snd_mixer_attach(mixer, dev)) < 0) { - pa_log_warn("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); + pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); return -1; } @@ -401,25 +696,28 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { 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); - assert(mixer); - assert(name); + pa_assert(mixer); + pa_assert(name); snd_mixer_selem_id_set_name(sid, name); if (!(elem = snd_mixer_find_selem(mixer, sid))) { - pa_log_warn("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(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)); } @@ -430,3 +728,391 @@ snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const 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', falling 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.", channel_map->channels); + + return 0; +} + +void pa_alsa_0dB_playback(snd_mixer_elem_t *elem) { + long min, max, v; + + pa_assert(elem); + + /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use + * raw volume levels and fix them to 75% */ + + if (snd_mixer_selem_set_playback_dB_all(elem, 0, -1) >= 0) + return; + + if (snd_mixer_selem_set_playback_dB_all(elem, 0, 1) >= 0) + return; + + if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0) + return; + + v = min + ((max - min) * 3) / 4; /* 75% */ + + if (v <= min) + v = max; + + snd_mixer_selem_set_playback_volume_all(elem, v); +} + +void pa_alsa_0dB_capture(snd_mixer_elem_t *elem) { + long min, max, v; + + pa_assert(elem); + + /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use + * raw volume levels and fix them to 75% */ + + if (snd_mixer_selem_set_capture_dB_all(elem, 0, -1) >= 0) + return; + + if (snd_mixer_selem_set_capture_dB_all(elem, 0, 1) >= 0) + return; + + if (snd_mixer_selem_get_capture_volume_range(elem, &min, &max) < 0) + return; + + v = min + ((max - min) * 3) / 4; /* 75% */ + + if (v <= min) + v = max; + + snd_mixer_selem_set_capture_volume_all(elem, v); +} + +void pa_alsa_dump(snd_pcm_t *pcm) { + int err; + snd_output_t *out; + + pa_assert(pcm); + + pa_assert_se(snd_output_buffer_open(&out) == 0); + + if ((err = snd_pcm_dump(pcm, out)) < 0) + pa_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); + else { + char *s = NULL; + snd_output_buffer_string(out, &s); + pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); + } + + pa_assert_se(snd_output_close(out) == 0); +} + +void pa_alsa_dump_status(snd_pcm_t *pcm) { + int err; + snd_output_t *out; + snd_pcm_status_t *status; + + pa_assert(pcm); + + snd_pcm_status_alloca(&status); + + pa_assert_se(snd_output_buffer_open(&out) == 0); + + pa_assert_se(snd_pcm_status(pcm, status) == 0); + + if ((err = snd_pcm_status_dump(status, out)) < 0) + pa_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); + else { + char *s = NULL; + snd_output_buffer_string(out, &s); + pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); + } + + pa_assert_se(snd_output_close(out) == 0); +} + +static void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) { + va_list ap; + + va_start(ap, fmt); + + pa_log_levelv_meta(PA_LOG_WARN, file, line, function, fmt, ap); + + va_end(ap); +} + +static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT(0); + +void pa_alsa_redirect_errors_inc(void) { + /* This is not really thread safe, but we do our best */ + + if (pa_atomic_inc(&n_error_handler_installed) == 0) + snd_lib_error_set_handler(alsa_error_handler); +} + +void pa_alsa_redirect_errors_dec(void) { + int r; + + pa_assert_se((r = pa_atomic_dec(&n_error_handler_installed)) >= 1); + + if (r == 1) + snd_lib_error_set_handler(NULL); +} + +void pa_alsa_init_proplist(pa_proplist *p, snd_pcm_info_t *pcm_info) { + + static const char * const alsa_class_table[SND_PCM_CLASS_LAST+1] = { + [SND_PCM_CLASS_GENERIC] = "generic", + [SND_PCM_CLASS_MULTI] = "multi", + [SND_PCM_CLASS_MODEM] = "modem", + [SND_PCM_CLASS_DIGITIZER] = "digitizer" + }; + static const char * const class_table[SND_PCM_CLASS_LAST+1] = { + [SND_PCM_CLASS_GENERIC] = "sound", + [SND_PCM_CLASS_MULTI] = NULL, + [SND_PCM_CLASS_MODEM] = "modem", + [SND_PCM_CLASS_DIGITIZER] = NULL + }; + static const char * const alsa_subclass_table[SND_PCM_SUBCLASS_LAST+1] = { + [SND_PCM_SUBCLASS_GENERIC_MIX] = "generic-mix", + [SND_PCM_SUBCLASS_MULTI_MIX] = "multi-mix" + }; + + snd_pcm_class_t class; + snd_pcm_subclass_t subclass; + const char *n, *id, *sdn; + char *cn = NULL, *lcn = NULL; + int card; + + pa_assert(p); + pa_assert(pcm_info); + + pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa"); + + class = snd_pcm_info_get_class(pcm_info); + if (class <= SND_PCM_CLASS_LAST) { + if (class_table[class]) + pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, class_table[class]); + if (alsa_class_table[class]) + pa_proplist_sets(p, "alsa.class", alsa_class_table[class]); + } + subclass = snd_pcm_info_get_subclass(pcm_info); + if (subclass <= SND_PCM_SUBCLASS_LAST) + if (alsa_subclass_table[subclass]) + pa_proplist_sets(p, "alsa.subclass", alsa_subclass_table[subclass]); + + if ((n = snd_pcm_info_get_name(pcm_info))) + pa_proplist_sets(p, "alsa.name", n); + + if ((id = snd_pcm_info_get_id(pcm_info))) + pa_proplist_sets(p, "alsa.id", id); + + pa_proplist_setf(p, "alsa.subdevice", "%u", snd_pcm_info_get_subdevice(pcm_info)); + if ((sdn = snd_pcm_info_get_subdevice_name(pcm_info))) + pa_proplist_sets(p, "alsa.subdevice_name", sdn); + + pa_proplist_setf(p, "alsa.device", "%u", snd_pcm_info_get_device(pcm_info)); + + if ((card = snd_pcm_info_get_card(pcm_info)) >= 0) { + pa_proplist_setf(p, "alsa.card", "%i", card); + + if (snd_card_get_name(card, &cn) >= 0) + pa_proplist_sets(p, "alsa.card_name", cn); + + if (snd_card_get_longname(card, &lcn) >= 0) + pa_proplist_sets(p, "alsa.long_card_name", lcn); + } + + if (cn && n) + pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s - %s", cn, n); + else if (cn) + pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, cn); + else if (n) + pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, n); + + free(lcn); + free(cn); +} + +int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { + snd_pcm_state_t state; + int err; + + pa_assert(pcm); + + 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"); + + state = snd_pcm_state(pcm); + pa_log_warn("PCM state is %s", snd_pcm_state_name(state)); + + /* Try to recover from this error */ + + switch (state) { + + case SND_PCM_STATE_XRUN: + if ((err = snd_pcm_recover(pcm, -EPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); + return -1; + } + break; + + case SND_PCM_STATE_SUSPENDED: + if ((err = snd_pcm_recover(pcm, -ESTRPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); + return -1; + } + break; + + default: + + snd_pcm_drop(pcm); + + if ((err = snd_pcm_prepare(pcm)) < 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); + return -1; + } + break; + } + + return 0; +} + +pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) { + int n, err; + struct pollfd *pollfd; + pa_rtpoll_item *item; + + pa_assert(pcm); + + if ((n = snd_pcm_poll_descriptors_count(pcm)) < 0) { + pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); + return NULL; + } + + item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, n); + pollfd = pa_rtpoll_item_get_pollfd(item, NULL); + + if ((err = snd_pcm_poll_descriptors(pcm, pollfd, n)) < 0) { + pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + pa_rtpoll_item_free(item); + return NULL; + } + + return item; +} diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h index 215844b4..4de8bcd2 100644 --- a/src/modules/alsa-util.h +++ b/src/modules/alsa-util.h @@ -1,21 +1,22 @@ #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 @@ -26,20 +27,71 @@ #include <pulse/sample.h> #include <pulse/mainloop-api.h> - #include <pulse/channelmap.h> +#include <pulse/proplist.h> -struct pa_alsa_fdlist; +#include <pulsecore/rtpoll.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_fdlist_init_pcm(struct pa_alsa_fdlist *fdl, snd_pcm_t *pcm_handle, pa_mainloop_api* m, void (*cb)(void *userdata), void *userdata); -int pa_alsa_fdlist_init_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, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched, + pa_bool_t require_exact_channel_number); -int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size); +int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); 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, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched); + +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, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched); + +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); + +void pa_alsa_0dB_playback(snd_mixer_elem_t *elem); +void pa_alsa_0dB_capture(snd_mixer_elem_t *elem); + +void pa_alsa_dump(snd_pcm_t *pcm); +void pa_alsa_dump_status(snd_pcm_t *pcm); + +void pa_alsa_redirect_errors_inc(void); +void pa_alsa_redirect_errors_dec(void); + +void pa_alsa_init_proplist(pa_proplist *p, snd_pcm_info_t *pcm_info); + +int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); + +pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll); + #endif diff --git a/src/modules/bt-proximity-helper.c b/src/modules/bt-proximity-helper.c new file mode 100644 index 00000000..3767f01c --- /dev/null +++ b/src/modules/bt-proximity-helper.c @@ -0,0 +1,202 @@ +/* + * Small SUID helper that allows us to ping a BT device. Borrows + * heavily from bluez-utils' l2ping, which is licensed as GPL2+ + * 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> + * + * + * 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 + * + */ + +#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 index 165ccff6..905be13f 100644 --- a/src/modules/dbus-util.c +++ b/src/modules/dbus-util.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,25 +24,25 @@ #include <config.h> #endif -#include <assert.h> -#include <pulsecore/log.h> -#include <pulsecore/props.h> #include <pulse/xmalloc.h> #include <pulse/timeval.h> +#include <pulsecore/log.h> +#include <pulsecore/props.h> #include "dbus-util.h" struct pa_dbus_connection { - int refcount; + 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 = (DBusConnection *) userdata; +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); @@ -49,14 +50,17 @@ static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) } /* DBusDispatchStatusFunction callback for the pa mainloop */ -static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, - void *userdata) -{ - pa_dbus_connection *c = (pa_dbus_connection*) userdata; +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: @@ -65,11 +69,13 @@ static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, } } -static pa_io_event_flags_t -get_watch_flags(DBusWatch *watch) -{ - unsigned int flags = dbus_watch_get_flags(watch); - pa_io_event_flags_t events = PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +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)) @@ -80,21 +86,22 @@ get_watch_flags(DBusWatch *watch) if (flags & DBUS_WATCH_WRITABLE) events |= PA_IO_EVENT_OUTPUT; - return events; + 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) -{ +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 = (DBusWatch*) userdata; + DBusWatch *watch = userdata; - assert(fd == dbus_watch_get_fd(watch)); +#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); + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); return; } @@ -111,10 +118,8 @@ static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, } /* 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 = (DBusTimeout*) userdata; +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; @@ -127,218 +132,195 @@ static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, } /* DBusAddWatchFunction callback for pa mainloop */ -static dbus_bool_t add_watch(DBusWatch *watch, void *data) -{ +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); pa_io_event *ev; - pa_core *c = (pa_core*) data; - ev = c->mainloop->io_new(c->mainloop, dbus_watch_get_fd(watch), - get_watch_flags(watch), - handle_io_event, (void*) watch); - if (NULL == ev) - return FALSE; + 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, (void*) ev, c->mainloop->io_free); */ - dbus_watch_set_data(watch, (void*) ev, NULL); + 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_io_event*) dbus_watch_get_data(watch); +static void remove_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); + pa_io_event *ev; - /* free the event */ - if (NULL != 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_io_event*) dbus_watch_get_data(watch); +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) -{ - struct timeval tv; +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); pa_time_event *ev; - pa_core *c = (pa_core*) data; + struct timeval tv; + + pa_assert(timeout); + pa_assert(c); if (!dbus_timeout_get_enabled(timeout)) return FALSE; - if (!pa_gettimeofday(&tv)) - return -1; - + pa_gettimeofday(&tv); pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); - ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, - (void*) timeout); - if (NULL == ev) - return FALSE; + ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, timeout); - /* dbus_timeout_set_data(timeout, (void*) ev, c->mainloop->time_free); */ - dbus_timeout_set_data(timeout, (void*) ev, NULL); + 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_time_event*) dbus_timeout_get_data(timeout); +static void remove_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(c); - /* free the event */ - if (NULL != ev) + if ((ev = dbus_timeout_get_data(timeout))) c->mainloop->time_free(ev); } /* DBusTimeoutToggledFunction callback for pa mainloop */ -static void toggle_timeout(DBusTimeout *timeout, void *data) -{ - struct timeval tv; - pa_core *c = (pa_core*) data; - pa_time_event *ev = (pa_time_event*) dbus_timeout_get_data(timeout); +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 { - /* disable the timeout */ + } else c->mainloop->time_restart(ev, NULL); - } } -static void -pa_dbus_connection_free(pa_dbus_connection *c) -{ - assert(c); - assert(!dbus_connection_get_is_connected(c->connection)); +static void wakeup_main(void *userdata) { + pa_dbus_connection *c = userdata; - /* 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_assert(c); -static void -wakeup_main(void *userdata) -{ - pa_dbus_connection *c = (pa_dbus_connection*) userdata; /* 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 = pa_xnew(pa_dbus_connection, 1); +static pa_dbus_connection* pa_dbus_connection_new(pa_core* c, DBusConnection *conn, const char* name) { + pa_dbus_connection *pconn; - pconn->refcount = 1; + 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, - (void*) 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) -{ - assert(c && c->connection); +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) -{ - assert(c); +void pa_dbus_connection_unref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); - /* non-zero refcount, still outstanding refs */ - if (--(c->refcount)) + if (PA_REFCNT_DEC(c) > 0) return; - /* refcount is zero */ if (dbus_connection_get_is_connected(c->connection)) { - /* disconnect as we have no more internal references */ 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)); + /* must process remaining messages, bit of a kludge to handle + * both unload and shutdown */ + while (dbus_connection_read_write_dispatch(c->connection, -1)); } - pa_dbus_connection_free(c); + + /* 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) -{ - assert(c); +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); - ++(c->refcount); + PA_REFCNT_INC(c); return c; } -pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, - DBusError *error) -{ - const char* name; +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; - switch (type) { - case DBUS_BUS_SYSTEM: - name = "dbus-connection-system"; - break; - case DBUS_BUS_SESSION: - name = "dbus-connection-session"; - break; - case DBUS_BUS_STARTER: - name = "dbus-connection-starter"; - break; - default: - assert(0); /* never reached */ - break; - } + pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); - if ((pconn = pa_property_get(c, name))) + if ((pconn = pa_property_get(c, prop_name[type]))) return pa_dbus_connection_ref(pconn); - /* else */ - conn = dbus_bus_get_private(type, error); - if (conn == NULL || dbus_error_is_set(error)) { + if (!(conn = dbus_bus_get_private(type, error))) return NULL; - } - pconn = pa_dbus_connection_new(c, conn, name); + pconn = pa_dbus_connection_new(c, conn, prop_name[type]); - /* don't exit on disconnect */ dbus_connection_set_exit_on_disconnect(conn, FALSE); - /* set up the DBUS call backs */ - dbus_connection_set_dispatch_status_function(conn, dispatch_status, - (void*) pconn, NULL); - dbus_connection_set_watch_functions(conn, - add_watch, - remove_watch, - toggle_watch, - (void*) c, NULL); - dbus_connection_set_timeout_functions(conn, - add_timeout, - remove_timeout, - toggle_timeout, - (void*) c, NULL); + 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 index 7a9871a4..2b24ac63 100644 --- a/src/modules/dbus-util.h +++ b/src/modules/dbus-util.h @@ -1,21 +1,21 @@ #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 diff --git a/src/modules/gconf/gconf-helper.c b/src/modules/gconf/gconf-helper.c index 40724f4e..f5016faf 100644 --- a/src/modules/gconf/gconf-helper.c +++ b/src/modules/gconf/gconf-helper.c @@ -1,18 +1,18 @@ -/* $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 @@ -30,6 +30,8 @@ #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" @@ -38,38 +40,38 @@ static void handle_module(GConfClient *client, const char *name) { gboolean enabled, locked; int i; - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name); + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name); locked = gconf_client_get_bool(client, p, FALSE); if (locked) return; - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/enabled", name); + 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; - - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/name%i", name, i); + + 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; - - snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/args%i", name, i); + + 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); } @@ -81,7 +83,7 @@ static void modules_callback( 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); @@ -111,17 +113,17 @@ int main(int argc, char *argv[]) { 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; diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c index d9f649fd..a2a43278 100644 --- a/src/modules/gconf/module-gconf.c +++ b/src/modules/gconf/module-gconf.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <string.h> #include <unistd.h> #include <stdlib.h> @@ -33,28 +32,22 @@ #include <sys/wait.h> #include <fcntl.h> -#ifdef HAVE_SYS_PRCTL_H -#include <sys/prctl.h> -#endif -#ifdef HAVE_SYS_RESOURCE_H -#include <sys/resource.h> -#endif - +#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 <pulse/xmalloc.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_USAGE("") +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 @@ -78,7 +71,7 @@ struct module_info { struct userdata { pa_core *core; pa_module *module; - + pa_hashmap *module_infos; pid_t pid; @@ -93,7 +86,7 @@ struct userdata { static int fill_buf(struct userdata *u) { ssize_t r; - assert(u); + pa_assert(u); if (u->buf_fill >= BUF_MAX) { pa_log("read buffer overflow"); @@ -109,25 +102,25 @@ static int fill_buf(struct userdata *u) { static int read_byte(struct userdata *u) { int ret; - assert(u); + pa_assert(u); if (u->buf_fill < 1) if (fill_buf(u) < 0) return -1; ret = u->buf[0]; - assert(u->buf_fill > 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) { - assert(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; @@ -141,13 +134,13 @@ static char *read_string(struct userdata *u) { } static void unload_one_module(struct userdata *u, struct module_info*m, unsigned i) { - assert(u); - assert(m); - assert(i < m->n_items); + 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; @@ -158,9 +151,9 @@ static void unload_one_module(struct userdata *u, struct module_info*m, unsigned static void unload_all_modules(struct userdata *u, struct module_info*m) { unsigned i; - - assert(u); - assert(m); + + pa_assert(u); + pa_assert(m); for (i = 0; i < m->n_items; i++) unload_one_module(u, m, i); @@ -177,11 +170,11 @@ static void load_module( int is_new) { pa_module *mod; - - assert(u); - assert(m); - assert(name); - assert(args); + + pa_assert(u); + pa_assert(m); + pa_assert(name); + pa_assert(args); if (!is_new) { if (m->items[i].index != PA_INVALID_INDEX && @@ -191,18 +184,18 @@ static void load_module( 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; } @@ -210,8 +203,8 @@ static void module_info_free(void *p, void *userdata) { struct module_info *m = p; struct userdata *u = userdata; - assert(m); - assert(u); + pa_assert(m); + pa_assert(u); unload_all_modules(u, m); pa_xfree(m->name); @@ -223,20 +216,23 @@ static int handle_event(struct userdata *u) { int ret = 0; do { - if ((opcode = read_byte(u)) < 0) + 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; @@ -280,16 +276,16 @@ static int handle_event(struct userdata *u) { /* 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; @@ -299,7 +295,7 @@ static int handle_event(struct userdata *u) { } pa_xfree(name); - + break; } } @@ -322,101 +318,22 @@ static void io_event_cb( 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); - } -} - -static int start_client(const char *n, 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 */ - close(pipe_fds[1]); - - if (pid) - *pid = child; - - return pipe_fds[0]; - } else { - int max_fd, i; - - /* child */ - - close(pipe_fds[0]); - dup2(pipe_fds[1], 1); - - if (pipe_fds[1] != 1) - close(pipe_fds[1]); - - close(0); - open("/dev/null", O_RDONLY); - - close(2); - open("/dev/null", O_WRONLY); - - 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++) - close(i); -#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 - - execl(n, n, NULL); - _exit(1); + pa_module_unload_request(u->module); } - -fail: - if (pipe_fds[0] >= 0) - close(pipe_fds[0]); - - if (pipe_fds[1] >= 0) - close(pipe_fds[1]); - - return -1; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u; int r; u = pa_xnew(struct userdata, 1); - u->core = c; + 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); @@ -425,17 +342,17 @@ int pa__init(pa_core *c, pa_module*m) { u->fd_type = 0; u->io_event = NULL; u->buf_fill = 0; - - if ((u->fd = start_client(PA_GCONF_HELPER, &u->pid)) < 0) + + if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0) goto fail; - - u->io_event = c->mainloop->io_new( - c->mainloop, + + 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; @@ -443,37 +360,36 @@ int pa__init(pa_core *c, pa_module*m) { /* Read until the client signalled us that it is ready with * initialization */ } while (r != 1); - + return 0; fail: - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; - if (u->io_event) - c->mainloop->io_free(u->io_event); - - if (u->fd >= 0) - close(u->fd); - 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 index 7bbd7de2..6765775a 100644 --- a/src/modules/module-alsa-sink.c +++ b/src/modules/module-alsa-sink.c @@ -1,18 +1,19 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,18 +24,13 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> -#ifdef HAVE_SYS_POLL_H -#include <sys/poll.h> -#else -#include "poll.h" -#endif - #include <asoundlib.h> #include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/timeval.h> #include <pulsecore/core.h> #include <pulsecore/module.h> @@ -44,514 +40,1479 @@ #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 <pulsecore/rtclock.h> +#include <pulsecore/time-smoother.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_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 card index> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map>") - -struct userdata { - snd_pcm_t *pcm_handle; - snd_mixer_t *mixer_handle; - snd_mixer_elem_t *mixer_elem; - pa_sink *sink; - struct pa_alsa_fdlist *pcm_fdl; - struct pa_alsa_fdlist *mixer_fdl; - long hw_volume_max, hw_volume_min; - - size_t frame_size, fragment_size; - pa_memchunk memchunk, silence; - pa_module *module; -}; + "mmap=<enable memory mapping?> " + "tsched=<enable system timer based scheduling mode?> " + "tsched_buffer_size=<buffer size when using timer based scheduling> " + "tsched_buffer_watermark=<lower fill watermark> " + "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>"); static const char* const valid_modargs[] = { - "device", "sink_name", + "device", + "device_id", "format", - "channels", "rate", + "channels", + "channel_map", "fragments", "fragment_size", - "channel_map", + "mmap", + "tsched", + "tsched_buffer_size", + "tsched_buffer_watermark", + "mixer_reset", NULL }; #define DEFAULT_DEVICE "default" +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ +#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ +#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, u->sink ? pa_sink_used_by(u->sink) : 0); +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; + long hw_dB_max, hw_dB_min; + pa_bool_t hw_dB_supported; + + size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; + unsigned nfragments; + pa_memchunk memchunk; + + char *device_name; + + pa_bool_t use_mmap, use_tsched; + + pa_bool_t first, after_rewind; + + pa_rtpoll_item *alsa_rtpoll_item; + + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; + + pa_smoother *smoother; + int64_t frame_index; + uint64_t since_start; + + snd_pcm_sframes_t hwbuf_unused_frames; +}; + +static void fix_tsched_watermark(struct userdata *u) { + size_t max_use; + size_t min_sleep, min_wakeup; + pa_assert(u); + + max_use = u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size; + + min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->sink->sample_spec); + min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->sink->sample_spec); + + if (min_sleep > max_use/2) + min_sleep = pa_frame_align(max_use/2, &u->sink->sample_spec); + if (min_sleep < u->frame_size) + min_sleep = u->frame_size; + + if (min_wakeup > max_use/2) + min_wakeup = pa_frame_align(max_use/2, &u->sink->sample_spec); + if (min_wakeup < u->frame_size) + min_wakeup = u->frame_size; + + if (u->tsched_watermark > max_use-min_sleep) + u->tsched_watermark = max_use-min_sleep; + + if (u->tsched_watermark < min_wakeup) + u->tsched_watermark = min_wakeup; } -static void clear_up(struct userdata *u) { - assert(u); - - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - } - - if (u->pcm_fdl) - pa_alsa_fdlist_free(u->pcm_fdl); - if (u->mixer_fdl) - pa_alsa_fdlist_free(u->mixer_fdl); +static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { + pa_usec_t usec, wm; - u->pcm_fdl = u->mixer_fdl = NULL; + pa_assert(sleep_usec); + pa_assert(process_usec); - if (u->mixer_handle) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } - - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; + pa_assert(u); + + usec = pa_sink_get_requested_latency_within_thread(u->sink); + + if (usec == (pa_usec_t) -1) + usec = pa_bytes_to_usec(u->hwbuf_size, &u->sink->sample_spec); + +/* pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */ + + wm = pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec); + + if (usec >= wm) { + *sleep_usec = usec - wm; + *process_usec = wm; + } else + *process_usec = *sleep_usec = usec / 2; + +/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */ +} + +static int try_recover(struct userdata *u, const char *call, int err) { + pa_assert(u); + pa_assert(call); + pa_assert(err < 0); + + pa_log_debug("%s: %s", call, snd_strerror(err)); + + pa_assert(err != -EAGAIN); + + if (err == -EPIPE) + pa_log_debug("%s: Buffer underrun!", call); + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { + u->first = TRUE; + u->since_start = 0; + return 0; } + + pa_log("%s: %s", call, snd_strerror(err)); + return -1; } -static int xrun_recovery(struct userdata *u) { - int ret; - assert(u); +static size_t check_left_to_play(struct userdata *u, snd_pcm_sframes_t n) { + size_t left_to_play; - pa_log_info("*** ALSA-XRUN (playback) ***"); - - if ((ret = snd_pcm_prepare(u->pcm_handle)) < 0) { - pa_log("snd_pcm_prepare() failed: %s", snd_strerror(-ret)); + if (n*u->frame_size < u->hwbuf_size) + left_to_play = u->hwbuf_size - (n*u->frame_size); + else + left_to_play = 0; - clear_up(u); - pa_module_unload_request(u->module); - return -1; + if (left_to_play > 0) { +/* pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC); */ + } else if (!u->first && !u->after_rewind) { + pa_log_info("Underrun!"); + + if (u->use_tsched) { + size_t old_watermark = u->tsched_watermark; + + u->tsched_watermark *= 2; + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_notice("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + } } - return ret; + return left_to_play; } -static void do_write(struct userdata *u) { - assert(u); +static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { + int work_done = 0; + pa_usec_t max_sleep_usec, process_usec; + size_t left_to_play; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); - update_usage(u); - for (;;) { - void *p; - pa_memchunk *memchunk = NULL; - snd_pcm_sframes_t frames; - - if (u->memchunk.memblock) - memchunk = &u->memchunk; - else { - if (pa_sink_render(u->sink, u->fragment_size, &u->memchunk) < 0) - memchunk = &u->silence; - else - memchunk = &u->memchunk; - } - - assert(memchunk->memblock); - assert(memchunk->length); - assert((memchunk->length % u->frame_size) == 0); - - p = pa_memblock_acquire(memchunk->memblock); - - if ((frames = snd_pcm_writei(u->pcm_handle, (uint8_t*) p + memchunk->index, memchunk->length / u->frame_size)) < 0) { - pa_memblock_release(memchunk->memblock); - - if (frames == -EAGAIN) - return; - - if (frames == -EPIPE) { - if (xrun_recovery(u) < 0) - return; - + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + /* First we determine how many samples are missing to fill the + * buffer up to 100% */ + + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) continue; + + return r; + } + + left_to_play = check_left_to_play(u, n); + + if (u->use_tsched) + + /* We won't fill up the playback buffer before at least + * half the sleep time is over because otherwise we might + * ask for more data from the clients then they expect. We + * need to guarantee that clients only have to keep around + * a single hw buffer length. */ + + if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) + break; + + n -= u->hwbuf_unused_frames; + +/* pa_log_debug("Filling up"); */ + + for (;;) { + pa_memchunk chunk; + void *p; + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; + +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ + + if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; + + return r; + } + + /* Make sure that if these memblocks need to be copied they will fit into one slot */ + if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size) + frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size; + + /* 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, TRUE); + 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 (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0) + continue; + + return r; } - pa_log("snd_pcm_writei() failed: %s", snd_strerror(-frames)); + work_done = 1; + + u->frame_index += frames; + u->since_start += frames * u->frame_size; + +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ + + if (frames >= (snd_pcm_uframes_t) n) + break; + + n -= frames; + } + } + + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; + return work_done; +} + +static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) { + int work_done = 0; + pa_usec_t max_sleep_usec, process_usec; + size_t left_to_play; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + + for (;;) { + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + continue; - clear_up(u); - pa_module_unload_request(u->module); - return; + return r; } - pa_memblock_release(memchunk->memblock); - + left_to_play = check_left_to_play(u, n); + + if (u->use_tsched) + + /* We won't fill up the playback buffer before at least + * half the sleep time is over because otherwise we might + * ask for more data from the clients then they expect. We + * need to guarantee that clients only have to keep around + * a single hw buffer length. */ + + if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) + break; + + n -= u->hwbuf_unused_frames; + + for (;;) { + snd_pcm_sframes_t frames; + void *p; + +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, n * u->frame_size, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + frames = u->memchunk.length / u->frame_size; + + if (frames > n) + frames = n; + + p = pa_memblock_acquire(u->memchunk.memblock); + frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, frames); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(frames != 0); + + if (PA_UNLIKELY(frames < 0)) { + + if ((r = try_recover(u, "snd_pcm_writei", n)) == 0) + continue; + + return r; + } - if (memchunk == &u->memchunk) { - size_t l = frames * u->frame_size; - memchunk->index += l; - memchunk->length -= l; + u->memchunk.index += frames * u->frame_size; + u->memchunk.length -= frames * u->frame_size; - if (memchunk->length == 0) { - pa_memblock_unref(memchunk->memblock); - memchunk->memblock = NULL; - memchunk->index = memchunk->length = 0; + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); } + + work_done = 1; + + u->frame_index += frames; + u->since_start += frames * u->frame_size; + +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ + + if (frames >= n) + break; + + n -= frames; } - - break; } + + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; + return work_done; } -static void fdl_callback(void *userdata) { - struct userdata *u = userdata; - assert(u); +static void update_smoother(struct userdata *u) { + snd_pcm_sframes_t delay = 0; + int64_t frames; + int err; + pa_usec_t now1, now2; +/* struct timeval timestamp; */ + snd_pcm_status_t *status; - if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN) - if (xrun_recovery(u) < 0) - return; + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_assert(u->pcm_handle); + + /* Let's update the time smoother */ + + snd_pcm_hwsync(u->pcm_handle); + snd_pcm_avail_update(u->pcm_handle); + +/* if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) { */ +/* pa_log("Failed to query DSP status data: %s", snd_strerror(err)); */ +/* return; */ +/* } */ + +/* delay = snd_pcm_status_get_delay(status); */ + + if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) { + pa_log("Failed to query DSP status data: %s", snd_strerror(err)); + return; + } - do_write(u); + frames = u->frame_index - delay; + +/* pa_log_debug("frame_index = %llu, delay = %llu, p = %llu", (unsigned long long) u->frame_index, (unsigned long long) delay, (unsigned long long) frames); */ + +/* snd_pcm_status_get_tstamp(status, ×tamp); */ +/* pa_rtclock_from_wallclock(×tamp); */ +/* now1 = pa_timeval_load(×tamp); */ + + now1 = pa_rtclock_usec(); + now2 = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec); + pa_smoother_put(u->smoother, now1, now2); } -static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { - struct userdata *u = snd_mixer_elem_get_callback_private(elem); +static pa_usec_t sink_get_latency(struct userdata *u) { + pa_usec_t r = 0; + int64_t delay; + pa_usec_t now1, now2; - assert(u && u->mixer_handle); + pa_assert(u); - if (mask == SND_CTL_EVENT_MASK_REMOVE) - return 0; + now1 = pa_rtclock_usec(); + now2 = pa_smoother_get(u->smoother, now1); - if (mask & SND_CTL_EVENT_MASK_VALUE) { - if (u->sink->get_hw_volume) - u->sink->get_hw_volume(u->sink); - if (u->sink->get_hw_mute) - u->sink->get_hw_mute(u->sink); - pa_subscription_post(u->sink->core, - PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, - u->sink->index); + delay = (int64_t) pa_bytes_to_usec(u->frame_index * u->frame_size, &u->sink->sample_spec) - (int64_t) now2; + + if (delay > 0) + r = (pa_usec_t) delay; + + 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) { + pa_assert(u); + pa_assert(u->pcm_handle); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) + return -1; + + return 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + + /* 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 pa_usec_t sink_get_latency_cb(pa_sink *s) { - pa_usec_t r = 0; - struct userdata *u = s->userdata; - snd_pcm_sframes_t frames; +static int update_sw_params(struct userdata *u) { + snd_pcm_uframes_t avail_min; int err; - - assert(s && u && u->sink); - if ((err = snd_pcm_delay(u->pcm_handle, &frames)) < 0) { - pa_log("failed to get delay: %s", snd_strerror(err)); - s->get_latency = NULL; - return 0; + pa_assert(u); + + /* Use the full buffer if noone asked us for anything specific */ + u->hwbuf_unused_frames = 0; + + if (u->use_tsched) { + pa_usec_t latency; + + if ((latency = pa_sink_get_requested_latency_within_thread(u->sink)) != (pa_usec_t) -1) { + size_t b; + + pa_log_debug("latency set to %0.2f", (double) latency / PA_USEC_PER_MSEC); + + b = pa_usec_to_bytes(latency, &u->sink->sample_spec); + + /* We need at least one sample in our buffer */ + + if (PA_UNLIKELY(b < u->frame_size)) + b = u->frame_size; + + u->hwbuf_unused_frames = + PA_LIKELY(b < u->hwbuf_size) ? + ((u->hwbuf_size - b) / u->frame_size) : 0; + + fix_tsched_watermark(u); + } } - if (frames < 0) - frames = 0; + pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); - r += pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec); + /* We need at last one frame in the used part of the buffer */ + avail_min = u->hwbuf_unused_frames + 1; - if (u->memchunk.memblock) - r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec); + if (u->use_tsched) { + pa_usec_t sleep_usec, process_usec; - return r; + hw_sleep_time(u, &sleep_usec, &process_usec); + avail_min += pa_usec_to_bytes(sleep_usec, &u->sink->sample_spec); + } + + pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); + + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + return err; + } + + pa_sink_set_max_request(u->sink, u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size); + + return 0; +} + +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err; + pa_bool_t b, d; + 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; + d = u->use_tsched; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; + } + + if (b != u->use_mmap || d != u->use_tsched) { + 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 (update_sw_params(u) < 0) + goto fail; + + if (build_pollfd(u) < 0) + goto fail; + + /* FIXME: We need to reload the volume somehow */ + + u->first = TRUE; + u->since_start = 0; + + 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_get_hw_volume_cb(pa_sink *s) { +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_IS_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; - assert(u); - assert(u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - for (i = 0; i < s->hw_volume.channels; i++) { - long set_vol, vol; + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; - assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); + 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, i, &vol)) < 0) - goto fail; + if (u->hw_dB_supported) { - set_vol = (long) roundf(((float) s->hw_volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) >= 0) { + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + continue; + } - /* Try to avoid superfluous volume changes */ - if (set_vol != vol) - s->hw_volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + u->hw_dB_supported = FALSE; + } + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_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_hw_volume = NULL; - s->set_hw_volume = NULL; + return -1; } -static int sink_set_hw_volume_cb(pa_sink *s) { +static int sink_set_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; int err; int i; - pa_volume_t vol; - assert(u); - assert(u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - for (i = 0; i < s->hw_volume.channels; i++) { + for (i = 0; i < s->sample_spec.channels; i++) { long alsa_vol; - - assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); + pa_volume_t vol; + + pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); - vol = s->hw_volume.values[i]; + vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); + + if (u->hw_dB_supported) { + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + + if ((err = snd_mixer_selem_set_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, -1)) >= 0) { + + if (snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + + continue; + } + + u->hw_dB_supported = FALSE; + + } - 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; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); - if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, i, alsa_vol)) < 0) + if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) goto fail; + + if (snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; fail: pa_log_error("Unable to set volume: %s", snd_strerror(err)); - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; + return -1; } -static int sink_get_hw_mute_cb(pa_sink *s) { +static int sink_get_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; int err, sw; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw); - if (err) { + 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_hw_mute = NULL; - s->set_hw_mute = NULL; return -1; } - s->hw_muted = !sw; + s->muted = !sw; return 0; } -static int sink_set_hw_mute_cb(pa_sink *s) { +static int sink_set_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; int err; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->hw_muted); - if (err) { + 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_hw_mute = NULL; - s->set_hw_mute = NULL; return -1; } return 0; } -int pa__init(pa_core *c, pa_module*m) { +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u = s->userdata; + snd_pcm_sframes_t before; + pa_assert(u); + + if (!u->pcm_handle) + return; + + before = u->hwbuf_unused_frames; + update_sw_params(u); + + /* Let's check whether we now use only a smaller part of the + buffer then before. If so, we need to make sure that subsequent + rewinds are relative to the new maxium fill level and not to the + current fill level. Thus, let's do a full rewind once, to clear + things up. */ + + if (u->hwbuf_unused_frames > before) { + pa_log_debug("Requesting rewind due to latency change."); + pa_sink_request_rewind(s, 0); + } +} + +static int process_rewind(struct userdata *u) { + snd_pcm_sframes_t unused; + size_t rewind_nbytes, unused_nbytes, limit_nbytes; + pa_assert(u); + + /* Figure out how much we shall rewind and reset the counter */ + rewind_nbytes = u->sink->thread_info.rewind_nbytes; + u->sink->thread_info.rewind_nbytes = 0; + + pa_assert(rewind_nbytes > 0); + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + snd_pcm_hwsync(u->pcm_handle); + if ((unused = snd_pcm_avail_update(u->pcm_handle)) < 0) { + pa_log("snd_pcm_avail_update() failed: %s", snd_strerror(unused)); + return -1; + } + + unused_nbytes = u->tsched_watermark + (size_t) unused * u->frame_size; + + if (u->hwbuf_size > unused_nbytes) + limit_nbytes = u->hwbuf_size - unused_nbytes; + else + limit_nbytes = 0; + + if (rewind_nbytes > limit_nbytes) + rewind_nbytes = limit_nbytes; + + if (rewind_nbytes > 0) { + snd_pcm_sframes_t in_frames, out_frames; + + pa_log_debug("Limited to %lu bytes.", (unsigned long) rewind_nbytes); + + in_frames = (snd_pcm_sframes_t) rewind_nbytes / u->frame_size; + pa_log_debug("before: %lu", (unsigned long) in_frames); + if ((out_frames = snd_pcm_rewind(u->pcm_handle, in_frames)) < 0) { + pa_log("snd_pcm_rewind() failed: %s", snd_strerror(out_frames)); + return -1; + } + pa_log_debug("after: %lu", (unsigned long) out_frames); + + rewind_nbytes = out_frames * u->frame_size; + + if (rewind_nbytes <= 0) + pa_log_info("Tried rewind, but was apparently not possible."); + else { + u->frame_index -= out_frames; + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); + pa_sink_process_rewind(u->sink, rewind_nbytes); + + u->after_rewind = TRUE; + } + } else + pa_log_debug("Mhmm, actually there is nothing to rewind."); + + 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; + +/* pa_log_debug("loop"); */ + + /* Render some data and write it to the dsp */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + int work_done; + pa_usec_t sleep_usec; + + if (u->sink->thread_info.rewind_nbytes > 0) + if (process_rewind(u) < 0) + goto fail; + + if (u->use_mmap) + work_done = mmap_write(u, &sleep_usec); + else + work_done = unix_write(u, &sleep_usec); + + if (work_done < 0) + goto fail; + +/* pa_log_debug("work_done = %i", work_done); */ + + if (work_done) { + + if (u->first) { + pa_log_info("Starting playback."); + snd_pcm_start(u->pcm_handle); + + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + } + + update_smoother(u); + } + + if (u->use_tsched) { + pa_usec_t cusec; + + if (u->since_start <= u->hwbuf_size) { + + /* USB devices on ALSA seem to hit a buffer + * underrun during the first iterations much + * quicker then we calculate here, probably due to + * the transport latency. To accomodate for that + * we artificially decrease the sleep time until + * we have filled the buffer at least once + * completely.*/ + + /*pa_log_debug("Cutting sleep time for the initial iterations by half.");*/ + sleep_usec /= 2; + } + + /* OK, the playback buffer is now full, let's + * calculate when to wake up next */ +/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */ + + /* Convert from the sound card time domain to the + * system time domain */ + cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); + +/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ + + /* We don't trust the conversion, so we wake up whatever comes first */ + pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); + } + + u->first = FALSE; + u->after_rewind = FALSE; + + } else if (u->use_tsched) + + /* OK, we're in an invalid state, let's disable our timers */ + 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; + + /* Tell ALSA about this and process its response */ + if (PA_SINK_IS_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 (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) + goto fail; + + u->first = TRUE; + u->since_start = 0; + } + + if (revents && u->use_tsched) + pa_log_debug("Wakeup from ALSA! (%i)", revents); + } + } + +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; - int ret = -1; struct userdata *u = NULL; - const char *dev; + const char *dev_id; pa_sample_spec ss; pa_channel_map map; - uint32_t periods, fragsize; - snd_pcm_uframes_t period_size; + uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; + snd_pcm_uframes_t period_frames, tsched_frames; 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 namereg_fail; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE; + pa_usec_t usec; + pa_sink_new_data data; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); + + pa_alsa_redirect_errors_inc(); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - ss = c->default_sample_spec; + 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"); + pa_log("Failed to parse sample specification and channel map"); goto fail; } frame_size = pa_frame_size(&ss); - - /* Fix latency to 100ms */ - periods = 8; - fragsize = pa_bytes_per_second(&ss)/128; - if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) { - pa_log("failed to parse buffer metrics"); + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); + if (frag_size <= 0) + frag_size = frame_size; + tsched_size = pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); + tsched_watermark = pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || + pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) { + pa_log("Failed to parse buffer metrics"); goto fail; } - period_size = fragsize/frame_size; - - u = pa_xnew0(struct userdata, 1); - m->userdata = u; - u->module = m; - - snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { - pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); + + hwbuf_size = frag_size * nfrags; + period_frames = frag_size/frame_size; + tsched_frames = tsched_size/frame_size; + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); goto fail; } - if ((err = snd_pcm_info_malloc(&pcm_info)) < 0 || - (err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { - pa_log("Error fetching PCM info: %s", snd_strerror(err)); + if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { + pa_log("Failed to parse timer_scheduling argument."); goto fail; } - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + if (use_tsched && !pa_rtclock_hrtimer()) { + pa_log("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + use_tsched = FALSE; + } + + if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) { + pa_log("Failed to parse mixer_reset argument."); goto fail; } - if (ss.channels != map.channels) - /* Seems ALSA didn't like the channel number, so let's fix the channel map */ - pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); - - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) { - pa_log("Error opening mixer: %s", snd_strerror(err)); + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + u->use_tsched = use_tsched; + u->first = TRUE; + u->since_start = 0; + u->after_rewind = FALSE; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->alsa_rtpoll_item = NULL; + + u->smoother = pa_smoother_new(DEFAULT_TSCHED_BUFFER_USEC*2, DEFAULT_TSCHED_BUFFER_USEC*2, TRUE, 5); + usec = pa_rtclock_usec(); + pa_smoother_set_time_offset(u->smoother, usec); + pa_smoother_pause(u->smoother, usec); + + snd_config_update_free_global(); + + b = use_mmap; + d = use_tsched; + + 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_frames, tsched_frames, + &b, &d))) + + 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_frames, tsched_frames, + &b, &d))) + 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 = FALSE; + } + + if (use_tsched && (!b || !d)) { + pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling."); + u->use_tsched = use_tsched = FALSE; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if (u->use_tsched) + pa_log_info("Successfully enabled timer-based scheduling 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 ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || - !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "PCM", "Master"))) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; + /* 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 { + snd_pcm_info_t *info; + + snd_pcm_info_alloca(&info); + + if (snd_pcm_info(u->pcm_handle, info) >= 0) { + char *md; + int card; + + if ((card = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card); + + 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; + namereg_fail = TRUE; else { - name = name_buf = pa_sprintf_malloc("alsa_output.%s", dev); - namereg_fail = 0; + name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name); + namereg_fail = FALSE; } - if (!(u->sink = pa_sink_new(c, __FILE__, name, namereg_fail, &ss, &map))) { + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, name); + data.namereg_fail = namereg_fail; + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + + pa_alsa_init_proplist(data.proplist, pcm_info); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + pa_xfree(name_buf); + + if (!u->sink) { pa_log("Failed to create sink object"); goto fail; } - u->sink->is_hardware = 1; - u->sink->get_latency = sink_get_latency_cb; + u->sink->parent.process_msg = sink_process_msg; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + u->frame_size = frame_size; + u->fragment_size = frag_size = period_frames * frame_size; + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + u->hwbuf_unused_frames = 0; + u->tsched_watermark = tsched_watermark; + u->frame_index = 0; + u->hw_dB_supported = FALSE; + u->hw_dB_min = u->hw_dB_max = 0; + u->hw_volume_min = u->hw_volume_max = 0; + + if (use_tsched) + fix_tsched_watermark(u); + + u->sink->thread_info.max_rewind = use_tsched ? u->hwbuf_size : 0; + u->sink->thread_info.max_request = u->hwbuf_size; + + pa_sink_set_latency_range(u->sink, + !use_tsched ? pa_bytes_to_usec(u->hwbuf_size, &ss) : (pa_usec_t) -1, + pa_bytes_to_usec(u->hwbuf_size, &ss)); + + pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", + nfrags, (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); + + if (use_tsched) + pa_log_info("Time scheduling watermark is %0.2fms", + (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); + + if (update_sw_params(u) < 0) + goto fail; + + pa_memchunk_reset(&u->memchunk); + if (u->mixer_handle) { - assert(u->mixer_elem); - if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { - int i; + pa_assert(u->mixer_elem); - for (i = 0;i < ss.channels;i++) { - if (!snd_mixer_selem_has_playback_channel(u->mixer_elem, i)) - break; - } + 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 && + snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) { - if (i == ss.channels) { - u->sink->get_hw_volume = sink_get_hw_volume_cb; - u->sink->set_hw_volume = sink_set_hw_volume_cb; - snd_mixer_selem_get_playback_volume_range( - u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); + pa_bool_t suitable = TRUE; + + pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); + + if (u->hw_volume_min > u->hw_volume_max) { + + pa_log_info("Minimal volume %li larger than maximum volume %li. Strange stuff Falling back to software volume control.", u->hw_volume_min, u->hw_volume_max); + suitable = FALSE; + + } else if (u->hw_volume_max - u->hw_volume_min < 3) { + + pa_log_info("Device has less than 4 volume levels. Falling back to software volume control."); + suitable = FALSE; + + } else if (snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) >= 0) { + + /* u->hw_dB_max = 0; u->hw_dB_min = -3000; Use this to make valgrind shut up */ + + pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", u->hw_dB_min/100.0, u->hw_dB_max/100.0); + + /* Let's see if this thing actually is useful for muting */ + if (u->hw_dB_min > -6000) { + pa_log_info("Device cannot attenuate for more than -60 dB (only %0.2f dB supported), falling back to software volume control.", ((double) u->hw_dB_min) / 100); + + suitable = FALSE; + } else if (u->hw_dB_max < 0) { + + pa_log_info("Device is still attenuated at maximum volume setting (%0.2f dB is maximum). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else if (u->hw_dB_min >= u->hw_dB_max) { + + pa_log_info("Minimal dB (%0.2f) larger or equal to maximum dB (%0.2f). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_min) / 100, ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else { + + if (u->hw_dB_max > 0) { + /* dB > 0 means overamplification, and clipping, we don't want that here */ + pa_log_info("Device can do overamplification for %0.2f dB. Limiting to 0 db", ((double) u->hw_dB_max) / 100); + u->hw_dB_max = 0; + } + + u->hw_dB_supported = TRUE; + } + } + + if (suitable) { + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0); + pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); + + } else if (mixer_reset) { + pa_log_info("Using software volume control. Trying to reset sound card to 0 dB."); + pa_alsa_0dB_playback(u->mixer_elem); + } else + pa_log_info("Using software volume control. Leaving hw mixer controls untouched."); } - } + if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { - u->sink->get_hw_mute = sink_get_hw_mute_cb; - u->sink->set_hw_mute = sink_set_hw_mute_cb; + u->sink->get_mute = sink_get_mute_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->flags |= PA_SINK_HW_MUTE_CTRL; } - } - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("ALSA PCM on %s (%s)", dev, snd_pcm_info_get_name(pcm_info))); - pa_xfree(t); - - u->pcm_fdl = pa_alsa_fdlist_new(); - assert(u->pcm_fdl); - if (pa_alsa_fdlist_init_pcm(u->pcm_fdl, u->pcm_handle, c->mainloop, fdl_callback, u) < 0) { - pa_log("failed to initialise file descriptor monitoring"); - goto fail; - } - if (u->mixer_handle) { u->mixer_fdl = pa_alsa_fdlist_new(); - assert(u->mixer_fdl); - if (pa_alsa_fdlist_init_mixer(u->mixer_fdl, u->mixer_handle, c->mainloop) < 0) { - pa_log("failed to initialise file descriptor monitoring"); + + 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; - - u->frame_size = frame_size; - u->fragment_size = period_size * frame_size; - - pa_log_info("using %u fragments of size %lu bytes.", periods, (long unsigned)u->fragment_size); - u->silence.memblock = pa_memblock_new(c->mempool, u->silence.length = u->fragment_size); - assert(u->silence.memblock); - pa_silence_memblock(u->silence.memblock, &ss); - u->silence.index = 0; + pa_alsa_dump(u->pcm_handle); - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; - - ret = 0; + 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_hw_volume) - u->sink->get_hw_volume(u->sink); - if (u->sink->get_hw_mute) - u->sink->get_hw_mute(u->sink); - -finish: + if (data.volume_is_set) { + if (u->sink->set_volume) + u->sink->set_volume(u->sink); + } else { + if (u->sink->get_volume) + u->sink->get_volume(u->sink); + } - pa_xfree(name_buf); - - if (ma) - pa_modargs_free(ma); + if (data.muted_is_set) { + if (u->sink->set_mute) + u->sink->set_mute(u->sink); + } else { + if (u->sink->get_mute) + u->sink->get_mute(u->sink); + } + + pa_sink_put(u->sink); - if (pcm_info) - snd_pcm_info_free(pcm_info); - - return ret; + pa_modargs_free(ma); + + return 0; fail: - - if (u) - pa__done(c, m); - goto finish; + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); - if (!(u = m->userdata)) + pa_assert(m); + + if (!(u = m->userdata)) { + pa_alsa_redirect_errors_dec(); 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); + } - clear_up(u); + 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->silence.memblock) - pa_memblock_unref(u->silence.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); + } + + if (u->smoother) + pa_smoother_free(u->smoother); + + pa_xfree(u->device_name); pa_xfree(u); -} + snd_config_update_free_global(); + + pa_alsa_redirect_errors_dec(); +} diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c index 9bde46da..1cc467d9 100644 --- a/src/modules/module-alsa-source.c +++ b/src/modules/module-alsa-source.c @@ -1,18 +1,19 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,18 +24,13 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> -#ifdef HAVE_SYS_POLL_H -#include <sys/poll.h> -#else -#include "poll.h" -#endif - #include <asoundlib.h> #include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/timeval.h> #include <pulsecore/core-error.h> #include <pulsecore/core.h> @@ -45,495 +41,1292 @@ #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 <pulsecore/time-smoother.h> +#include <pulsecore/rtclock.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_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 card index> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map>") - -struct userdata { - snd_pcm_t *pcm_handle; - snd_mixer_t *mixer_handle; - snd_mixer_elem_t *mixer_elem; - pa_source *source; - struct pa_alsa_fdlist *pcm_fdl; - struct pa_alsa_fdlist *mixer_fdl; - long hw_volume_max, hw_volume_min; - - size_t frame_size, fragment_size; - pa_memchunk memchunk; - pa_module *module; -}; + "mmap=<enable memory mapping?> " + "tsched=<enable system timer based scheduling mode?> " + "tsched_buffer_size=<buffer size when using timer based scheduling> " + "tsched_buffer_watermark=<upper fill watermark> " + "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>"); static const char* const valid_modargs[] = { - "device", "source_name", - "channels", - "rate", + "device", + "device_id", "format", + "rate", + "channels", + "channel_map", "fragments", "fragment_size", - "channel_map", + "mmap", + "tsched", + "tsched_buffer_size", + "tsched_buffer_watermark", + "mixer_reset", NULL }; #define DEFAULT_DEVICE "default" +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ +#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ +#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ + +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; + long hw_dB_max, hw_dB_min; + pa_bool_t hw_dB_supported; + + size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; + unsigned nfragments; + + char *device_name; + + pa_bool_t use_mmap, use_tsched; + + pa_rtpoll_item *alsa_rtpoll_item; + + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; + + pa_smoother *smoother; + int64_t frame_index; -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, u->source ? pa_source_used_by(u->source) : 0); + snd_pcm_sframes_t hwbuf_unused_frames; +}; + +static void fix_tsched_watermark(struct userdata *u) { + size_t max_use; + size_t min_sleep, min_wakeup; + pa_assert(u); + + max_use = u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size; + + min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->source->sample_spec); + min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec); + + if (min_sleep > max_use/2) + min_sleep = pa_frame_align(max_use/2, &u->source->sample_spec); + if (min_sleep < u->frame_size) + min_sleep = u->frame_size; + + if (min_wakeup > max_use/2) + min_wakeup = pa_frame_align(max_use/2, &u->source->sample_spec); + if (min_wakeup < u->frame_size) + min_wakeup = u->frame_size; + + if (u->tsched_watermark > max_use-min_sleep) + u->tsched_watermark = max_use-min_sleep; + + if (u->tsched_watermark < min_wakeup) + u->tsched_watermark = min_wakeup; } -static void clear_up(struct userdata *u) { - assert(u); - - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - } - - if (u->pcm_fdl) - pa_alsa_fdlist_free(u->pcm_fdl); - if (u->mixer_fdl) - pa_alsa_fdlist_free(u->mixer_fdl); +static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { + pa_usec_t wm, usec; - u->pcm_fdl = u->mixer_fdl = NULL; + pa_assert(u); - if (u->mixer_handle) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } - - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; + usec = pa_source_get_requested_latency_within_thread(u->source); + + if (usec == (pa_usec_t) -1) + usec = pa_bytes_to_usec(u->hwbuf_size, &u->source->sample_spec); + +/* pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */ + + wm = pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec); + + if (usec >= wm) { + *sleep_usec = usec - wm; + *process_usec = wm; + } else + *process_usec = *sleep_usec = usec /= 2; + +/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */ + + return usec; +} + +static int try_recover(struct userdata *u, const char *call, int err) { + pa_assert(u); + pa_assert(call); + pa_assert(err < 0); + + pa_log_debug("%s: %s", call, snd_strerror(err)); + + pa_assert(err != -EAGAIN); + + if (err == -EPIPE) + pa_log_debug("%s: Buffer overrun!", call); + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { + snd_pcm_start(u->pcm_handle); + return 0; } + + pa_log("%s: %s", call, snd_strerror(err)); + return -1; } -static int xrun_recovery(struct userdata *u) { - int ret; - assert(u); +static size_t check_left_to_record(struct userdata *u, snd_pcm_sframes_t n) { + size_t left_to_record; - pa_log_info("*** ALSA-XRUN (capture) ***"); - - if ((ret = snd_pcm_prepare(u->pcm_handle)) < 0) { - pa_log("snd_pcm_prepare() failed: %s", snd_strerror(-ret)); + if (n*u->frame_size < u->hwbuf_size) + left_to_record = u->hwbuf_size - (n*u->frame_size); + else + left_to_record = 0; - clear_up(u); - pa_module_unload_request(u->module); + if (left_to_record > 0) { +/* pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); */ + } else { + pa_log_info("Overrun!"); - return -1; + if (u->use_tsched) { + size_t old_watermark = u->tsched_watermark; + + u->tsched_watermark *= 2; + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_notice("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); + } } - return 0; + return left_to_record; } -static void do_read(struct userdata *u) { - assert(u); +static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { + int work_done = 0; + pa_usec_t max_sleep_usec, process_usec; + size_t left_to_record; + + pa_assert(u); + pa_source_assert_ref(u->source); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); - update_usage(u); - for (;;) { - pa_memchunk post_memchunk; - snd_pcm_sframes_t frames; - size_t l; - void *p; - - if (!u->memchunk.memblock) { - u->memchunk.memblock = pa_memblock_new(u->source->core->mempool, u->memchunk.length = u->fragment_size); - u->memchunk.index = 0; + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + continue; + + return r; } - - assert(u->memchunk.memblock); - assert(u->memchunk.length); - assert(u->memchunk.length % u->frame_size == 0); - - p = pa_memblock_acquire(u->memchunk.memblock); - - if ((frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p + u->memchunk.index, u->memchunk.length / u->frame_size)) < 0) { - pa_memblock_release(u->memchunk.memblock); - - if (frames == -EAGAIN) - return; - - if (frames == -EPIPE) { - if (xrun_recovery(u) < 0) - return; - + + left_to_record = check_left_to_record(u, n); + + if (u->use_tsched) + if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= 0)) + break; + + for (;;) { + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; + pa_memchunk chunk; + void *p; + +/* pa_log_debug("%lu frames to read", (unsigned long) frames); */ + + if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; + + return r; + } + + /* Make sure that if these memblocks need to be copied they will fit into one slot */ + if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size) + frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size; + + /* 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, TRUE); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_source_post(u->source, &chunk); + pa_memblock_unref_fixed(chunk.memblock); + + if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0) + continue; + + return r; + } + + work_done = 1; + + u->frame_index += frames; + +/* pa_log_debug("read %lu frames", (unsigned long) frames); */ + + if (frames >= (snd_pcm_uframes_t) n) + break; + + n -= frames; + } + } + + *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; + return work_done; +} + +static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { + int work_done = 0; + pa_usec_t max_sleep_usec, process_usec; + size_t left_to_record; + + pa_assert(u); + pa_source_assert_ref(u->source); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + + for (;;) { + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) continue; + + return r; + } + + left_to_record = check_left_to_record(u, n); + + if (u->use_tsched) + if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= 0)) + return work_done; + + for (;;) { + void *p; + snd_pcm_sframes_t frames; + pa_memchunk chunk; + + chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + + frames = pa_memblock_get_length(chunk.memblock) / u->frame_size; + + if (frames > n) + frames = n; + +/* pa_log_debug("%lu frames to read", (unsigned long) n); */ + + p = pa_memblock_acquire(chunk.memblock); + frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, frames); + pa_memblock_release(chunk.memblock); + + pa_assert(frames != 0); + + if (PA_UNLIKELY(frames < 0)) { + pa_memblock_unref(chunk.memblock); + + if ((r = try_recover(u, "snd_pcm_readi", n)) == 0) + continue; + + return r; } - pa_log("snd_pcm_readi() failed: %s", snd_strerror(-frames)); + chunk.index = 0; + chunk.length = frames * u->frame_size; + + pa_source_post(u->source, &chunk); + pa_memblock_unref(chunk.memblock); + + work_done = 1; + + u->frame_index += frames; - clear_up(u); - pa_module_unload_request(u->module); - return; +/* pa_log_debug("read %lu frames", (unsigned long) frames); */ + + if (frames >= n) + break; + + n -= frames; } - pa_memblock_release(u->memchunk.memblock); - - l = frames * u->frame_size; - - post_memchunk = u->memchunk; - post_memchunk.length = l; - - pa_source_post(u->source, &post_memchunk); - - u->memchunk.index += l; - u->memchunk.length -= l; - - if (u->memchunk.length == 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; + } + + *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; + return work_done; +} + +static void update_smoother(struct userdata *u) { + snd_pcm_sframes_t delay = 0; + int64_t frames; + int err; + pa_usec_t now1, now2; + + pa_assert(u); + pa_assert(u->pcm_handle); + + /* Let's update the time smoother */ + + snd_pcm_hwsync(u->pcm_handle); + snd_pcm_avail_update(u->pcm_handle); + + if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) { + pa_log_warn("Failed to get delay: %s", snd_strerror(err)); + return; + } + + frames = u->frame_index + delay; + + now1 = pa_rtclock_usec(); + now2 = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec); + + pa_smoother_put(u->smoother, now1, now2); +} + +static pa_usec_t source_get_latency(struct userdata *u) { + pa_usec_t r = 0; + int64_t delay; + pa_usec_t now1, now2; + + pa_assert(u); + + now1 = pa_rtclock_usec(); + now2 = pa_smoother_get(u->smoother, now1); + + delay = (int64_t) now2 - pa_bytes_to_usec(u->frame_index * u->frame_size, &u->source->sample_spec); + + if (delay > 0) + r = (pa_usec_t) delay; + + return r; +} + +static int build_pollfd(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) + return -1; + + return 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + + /* 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 update_sw_params(struct userdata *u) { + snd_pcm_uframes_t avail_min; + int err; + + pa_assert(u); + + /* Use the full buffer if noone asked us for anything specific */ + u->hwbuf_unused_frames = 0; + + if (u->use_tsched) { + pa_usec_t latency; + + if ((latency = pa_source_get_requested_latency_within_thread(u->source)) != (pa_usec_t) -1) { + size_t b; + + pa_log_debug("latency set to %0.2f", (double) latency / PA_USEC_PER_MSEC); + + b = pa_usec_to_bytes(latency, &u->source->sample_spec); + + /* We need at least one sample in our buffer */ + + if (PA_UNLIKELY(b < u->frame_size)) + b = u->frame_size; + + u->hwbuf_unused_frames = + PA_LIKELY(b < u->hwbuf_size) ? + ((u->hwbuf_size - b) / u->frame_size) : 0; + + fix_tsched_watermark(u); } - - break; } + + pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); + + avail_min = 1; + + if (u->use_tsched) { + pa_usec_t sleep_usec, process_usec; + + hw_sleep_time(u, &sleep_usec, &process_usec); + avail_min += pa_usec_to_bytes(sleep_usec, &u->source->sample_spec); + } + + pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); + + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + return err; + } + + return 0; } -static void fdl_callback(void *userdata) { - struct userdata *u = userdata; - assert(u); +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err; + pa_bool_t b, d; + unsigned nfrags; + snd_pcm_uframes_t period_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); - if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN) - if (xrun_recovery(u) < 0) - return; + 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; + d = u->use_tsched; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; + } + + if (b != u->use_mmap || d != u->use_tsched) { + 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 (update_sw_params(u) < 0) + goto fail; + + if (build_pollfd(u) < 0) + goto fail; + + /* FIXME: We need to reload the volume somehow */ + + snd_pcm_start(u->pcm_handle); + pa_smoother_resume(u->smoother, pa_rtclock_usec()); - do_read(u); + 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_IS_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); - assert(u && u->mixer_handle); + pa_assert(u); + pa_assert(u->mixer_handle); if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; if (mask & SND_CTL_EVENT_MASK_VALUE) { - if (u->source->get_hw_volume) - u->source->get_hw_volume(u->source); - if (u->source->get_hw_mute) - u->source->get_hw_mute(u->source); - - pa_subscription_post(u->source->core, - PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, - u->source->index); + pa_source_get_volume(u->source); + pa_source_get_mute(u->source); } return 0; } -static pa_usec_t source_get_latency_cb(pa_source *s) { +static int source_get_volume_cb(pa_source *s) { struct userdata *u = s->userdata; - snd_pcm_sframes_t frames; - assert(s && u && u->source); + int err; + int i; - if (snd_pcm_delay(u->pcm_handle, &frames) < 0) { - pa_log("failed to get delay"); - s->get_latency = NULL; - return 0; - } + pa_assert(u); + pa_assert(u->mixer_elem); - return pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec); -} + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; -static int source_get_hw_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; - long vol; - int err; - int i; + pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); + + if (u->hw_dB_supported) { + + if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) >= 0) { + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + continue; + } - assert(u && u->mixer_elem); + u->hw_dB_supported = FALSE; + } - for (i = 0;i < s->hw_volume.channels;i++) { - long set_vol; - - assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); - - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, i, &vol)) < 0) + if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) goto fail; - set_vol = (long) roundf(((float) s->hw_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->hw_volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_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_hw_volume = NULL; - s->set_hw_volume = NULL; + return -1; } -static int source_set_hw_volume_cb(pa_source *s) { +static int source_set_volume_cb(pa_source *s) { struct userdata *u = s->userdata; int err; - pa_volume_t vol; int i; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - for (i = 0;i < s->hw_volume.channels;i++) { - assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + pa_volume_t vol; - vol = s->hw_volume.values[i]; + pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); - if (vol > PA_VOLUME_NORM) - vol = PA_VOLUME_NORM; + vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); - vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + if (u->hw_dB_supported) { + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, i, vol)) < 0) + + if ((err = snd_mixer_selem_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, -1)) >= 0) { + + if (snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + + continue; + } + + u->hw_dB_supported = FALSE; + } + + alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); + + if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) goto fail; + + if (snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; fail: pa_log_error("Unable to set volume: %s", snd_strerror(err)); - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; + return -1; } -static int source_get_hw_mute_cb(pa_source *s) { +static int source_get_mute_cb(pa_source *s) { struct userdata *u = s->userdata; int err, sw; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw); - if (err) { + 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_hw_mute = NULL; - s->set_hw_mute = NULL; return -1; } - s->hw_muted = !sw; + s->muted = !sw; return 0; } -static int source_set_hw_mute_cb(pa_source *s) { +static int source_set_mute_cb(pa_source *s) { struct userdata *u = s->userdata; int err; - assert(u && u->mixer_elem); + pa_assert(u); + pa_assert(u->mixer_elem); - err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->hw_muted); - if (err) { + 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_hw_mute = NULL; - s->set_hw_mute = NULL; return -1; } return 0; } -int pa__init(pa_core *c, pa_module*m) { +static void source_update_requested_latency_cb(pa_source *s) { + struct userdata *u = s->userdata; + pa_assert(u); + + if (!u->pcm_handle) + return; + + update_sw_params(u); +} + +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; + +/* pa_log_debug("loop"); */ + + /* Read some data and pass it to the sources */ + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + int work_done = 0; + pa_usec_t sleep_usec; + + if (u->use_mmap) + work_done = mmap_read(u, &sleep_usec); + else + work_done = unix_read(u, &sleep_usec); + + if (work_done < 0) + goto fail; + +/* pa_log_debug("work_done = %i", work_done); */ + + if (work_done) + update_smoother(u); + + if (u->use_tsched) { + pa_usec_t cusec; + + /* OK, the capture buffer is now empty, let's + * calculate when to wake up next */ + +/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */ + + /* Convert from the sound card time domain to the + * system time domain */ + cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); + +/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ + + /* We don't trust the conversion, so we wake up whatever comes first */ + pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); + } + } else if (u->use_tsched) + + /* OK, we're in an invalid state, let's disable our timers */ + 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; + + /* Tell ALSA about this and process its response */ + if (PA_SOURCE_IS_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 (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) + goto fail; + + snd_pcm_start(u->pcm_handle); + } + + if (revents && u->use_tsched) + pa_log_debug("Wakeup from ALSA! (%i)", revents); + } + } + +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; - int ret = -1; struct userdata *u = NULL; - const char *dev; + const char *dev_id; pa_sample_spec ss; pa_channel_map map; - unsigned periods, fragsize; - snd_pcm_uframes_t period_size; + uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; + snd_pcm_uframes_t period_frames, tsched_frames; 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 namereg_fail; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE; + pa_source_new_data data; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); + + pa_alsa_redirect_errors_inc(); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - ss = c->default_sample_spec; + 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"); + pa_log("Failed to parse sample specification"); goto fail; } frame_size = pa_frame_size(&ss); - /* Fix latency to 100ms */ - periods = 12; - fragsize = pa_bytes_per_second(&ss)/128; - - if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) { - pa_log("failed to parse buffer metrics"); + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); + if (frag_size <= 0) + frag_size = frame_size; + tsched_size = pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); + tsched_watermark = pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || + pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) { + pa_log("Failed to parse buffer metrics"); goto fail; } - period_size = fragsize/frame_size; - - u = pa_xnew0(struct userdata, 1); - m->userdata = u; - u->module = m; - - snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { - pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); + + hwbuf_size = frag_size * nfrags; + period_frames = frag_size/frame_size; + tsched_frames = tsched_size/frame_size; + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); goto fail; } - if ((err = snd_pcm_info_malloc(&pcm_info)) < 0 || - (err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { - pa_log("Error fetching PCM info: %s", snd_strerror(err)); + if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { + pa_log("Failed to parse timer_scheduling argument."); goto fail; } - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + if (use_tsched && !pa_rtclock_hrtimer()) { + pa_log("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + use_tsched = FALSE; + } + + if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) { + pa_log("Failed to parse mixer_reset argument."); goto fail; } - if (ss.channels != map.channels) - /* Seems ALSA didn't like the channel number, so let's fix the channel map */ - pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + u->use_tsched = use_tsched; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->alsa_rtpoll_item = NULL; + + u->smoother = pa_smoother_new(DEFAULT_TSCHED_WATERMARK_USEC, DEFAULT_TSCHED_WATERMARK_USEC, TRUE, 5); + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) { - pa_log("Error opening mixer: %s", snd_strerror(err)); + snd_config_update_free_global(); + + b = use_mmap; + d = use_tsched; + + 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_frames, tsched_frames, + &b, &d))) + 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_frames, tsched_frames, + &b, &d))) + 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 = FALSE; + } + + if (use_tsched && (!b || !d)) { + pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling."); + u->use_tsched = use_tsched = FALSE; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if (u->use_tsched) + pa_log_info("Successfully enabled timer-based scheduling 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 ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || - !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic"))) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; + /* 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 { + snd_pcm_info_t* info; + + snd_pcm_info_alloca(&info); + + if (snd_pcm_info(u->pcm_handle, info) >= 0) { + char *md; + int card; + + if ((card = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card); + + 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; + namereg_fail = TRUE; else { - name = name_buf = pa_sprintf_malloc("alsa_input.%s", dev); - namereg_fail = 0; + name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name); + namereg_fail = FALSE; } - - if (!(u->source = pa_source_new(c, __FILE__, name, namereg_fail, &ss, &map))) { + + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_name(&data, name); + data.namereg_fail = namereg_fail; + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + + pa_alsa_init_proplist(data.proplist, pcm_info); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + pa_xfree(name_buf); + + if (!u->source) { pa_log("Failed to create source object"); goto fail; } - u->source->is_hardware = 1; + u->source->parent.process_msg = source_process_msg; + u->source->update_requested_latency = source_update_requested_latency_cb; u->source->userdata = u; - u->source->get_latency = source_get_latency_cb; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + u->frame_size = frame_size; + u->fragment_size = frag_size = period_frames * frame_size; + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + u->hwbuf_unused_frames = 0; + u->tsched_watermark = tsched_watermark; + u->frame_index = 0; + u->hw_dB_supported = FALSE; + u->hw_dB_min = u->hw_dB_max = 0; + u->hw_volume_min = u->hw_volume_max = 0; + + if (use_tsched) + fix_tsched_watermark(u); + + pa_source_set_latency_range(u->source, + !use_tsched ? pa_bytes_to_usec(u->hwbuf_size, &ss) : (pa_usec_t) -1, + pa_bytes_to_usec(u->hwbuf_size, &ss)); + + pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", + nfrags, (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); + + if (use_tsched) + pa_log_info("Time scheduling watermark is %0.2fms", + (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); + + if (update_sw_params(u) < 0) + goto fail; + if (u->mixer_handle) { - assert(u->mixer_elem); - if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { - int i; + pa_assert(u->mixer_elem); - for (i = 0;i < ss.channels;i++) { - if (!snd_mixer_selem_has_capture_channel(u->mixer_elem, i)) - break; - } + 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 && + snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) { + + pa_bool_t suitable = TRUE; + + pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); + + if (u->hw_volume_min > u->hw_volume_max) { + + pa_log_info("Minimal volume %li larger than maximum volume %li. Strange stuff Falling back to software volume control.", u->hw_volume_min, u->hw_volume_max); + suitable = FALSE; + + } else if (u->hw_volume_max - u->hw_volume_min < 3) { + + pa_log_info("Device has less than 4 volume levels. Falling back to software volume control."); + suitable = FALSE; + + } else if (snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) >= 0) { + + pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", u->hw_dB_min/100.0, u->hw_dB_max/100.0); + + /* Let's see if this thing actually is useful for muting */ + if (u->hw_dB_min > -6000) { + pa_log_info("Device cannot attenuate for more than -60 dB (only %0.2f dB supported), falling back to software volume control.", ((double) u->hw_dB_min) / 100); + + suitable = FALSE; + } else if (u->hw_dB_max < 0) { + + pa_log_info("Device is still attenuated at maximum volume setting (%0.2f dB is maximum). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else if (u->hw_dB_min >= u->hw_dB_max) { + + pa_log_info("Minimal dB (%0.2f) larger or equal to maximum dB (%0.2f). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_min) / 100, ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else + u->hw_dB_supported = TRUE; + } + + if (suitable) { + u->source->get_volume = source_get_volume_cb; + u->source->set_volume = source_set_volume_cb; + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SOURCE_DECIBEL_VOLUME : 0); + pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); + + } else if (mixer_reset) { + pa_log_info("Using software volume control. Trying to reset sound card to 0 dB."); + pa_alsa_0dB_capture(u->mixer_elem); + } else + pa_log_info("Using software volume control. Leaving hw mixer controls untouched."); - if (i == ss.channels) { - u->source->get_hw_volume = source_get_hw_volume_cb; - u->source->set_hw_volume = source_set_hw_volume_cb; - snd_mixer_selem_get_capture_volume_range( - u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); } - } + + if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { - u->source->get_hw_mute = source_get_hw_mute_cb; - u->source->set_hw_mute = source_set_hw_mute_cb; + u->source->get_mute = source_get_mute_cb; + u->source->set_mute = source_set_mute_cb; + u->source->flags |= PA_SOURCE_HW_MUTE_CTRL; } - } - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("ALSA PCM on %s (%s)", dev, snd_pcm_info_get_name(pcm_info))); - pa_xfree(t); - - u->pcm_fdl = pa_alsa_fdlist_new(); - assert(u->pcm_fdl); - if (pa_alsa_fdlist_init_pcm(u->pcm_fdl, u->pcm_handle, c->mainloop, fdl_callback, u) < 0) { - pa_log("failed to initialise file descriptor monitoring"); - goto fail; - } - if (u->mixer_handle) { u->mixer_fdl = pa_alsa_fdlist_new(); - assert(u->mixer_fdl); - if (pa_alsa_fdlist_init_mixer(u->mixer_fdl, u->mixer_handle, c->mainloop) < 0) { - pa_log("failed to initialise file descriptor monitoring"); + + 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; - u->frame_size = frame_size; - u->fragment_size = period_size * frame_size; + pa_alsa_dump(u->pcm_handle); - pa_log_info("using %u fragments of size %lu bytes.", periods, (long unsigned) u->fragment_size); + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + /* Get initial mixer settings */ + if (data.volume_is_set) { + if (u->source->set_volume) + u->source->set_volume(u->source); + } else { + if (u->source->get_volume) + u->source->get_volume(u->source); + } - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; + if (data.muted_is_set) { + if (u->source->set_mute) + u->source->set_mute(u->source); + } else { + if (u->source->get_mute) + u->source->get_mute(u->source); + } - snd_pcm_start(u->pcm_handle); - - ret = 0; + pa_source_put(u->source); - /* Get initial mixer settings */ - if (u->source->get_hw_volume) - u->source->get_hw_volume(u->source); - if (u->source->get_hw_mute) - u->source->get_hw_mute(u->source); + pa_modargs_free(ma); -finish: - pa_xfree(name_buf); + return 0; - if (ma) - pa_modargs_free(ma); +fail: - if (pcm_info) - snd_pcm_info_free(pcm_info); - - return ret; + if (ma) + pa_modargs_free(ma); -fail: - - if (u) - pa__done(c, m); + pa__done(m); - goto finish; + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); - if (!(u = m->userdata)) + pa_assert(m); + + if (!(u = m->userdata)) { + pa_alsa_redirect_errors_dec(); 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); - clear_up(u); - - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); - + 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); + } + + if (u->smoother) + pa_smoother_free(u->smoother); + + pa_xfree(u->device_name); pa_xfree(u); -} + snd_config_update_free_global(); + pa_alsa_redirect_errors_dec(); +} diff --git a/src/modules/module-always-sink.c b/src/modules/module-always-sink.c new file mode 100644 index 00000000..8b67a36d --- /dev/null +++ b/src/modules/module-always-sink.c @@ -0,0 +1,178 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> + +#include "module-always-sink-symdef.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Always keeps at least one sink loaded even if it's a null one"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "sink_name=<name of sink>"); + +#define DEFAULT_SINK_NAME "auto_null" + +static const char* const valid_modargs[] = { + "sink_name", + NULL, +}; + +struct userdata { + pa_hook_slot *put_slot, *unlink_slot; + pa_module* null_module; + pa_bool_t ignore; + char *sink_name; +}; + +static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata* u) { + pa_sink *target; + uint32_t idx; + char *t; + + pa_assert(c); + pa_assert(u); + pa_assert(!u->null_module); + + /* Loop through all sinks and check to see if we have *any* + * sinks. Ignore the sink passed in (if it's not null) */ + for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) + if (!sink || target != sink) + break; + + if (target) + return; + + pa_log_debug("Autoloading null-sink as no other sinks detected."); + + u->ignore = TRUE; + + t = pa_sprintf_malloc("sink_name=%s", u->sink_name); + u->null_module = pa_module_load(c, "module-null-sink", t); + pa_xfree(t); + + u->ignore = FALSE; + + if (!u->null_module) + pa_log_warn("Unable to load module-null-sink"); +} + +static pa_hook_result_t put_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + + /* This is us detecting ourselves on load... just ignore this. */ + if (u->ignore) + return PA_HOOK_OK; + + /* Auto-loaded null-sink not active, so ignoring newly detected sink. */ + if (!u->null_module) + return PA_HOOK_OK; + + /* This is us detecting ourselves on load in a different way... just ignore this too. */ + if (sink->module == u->null_module) + return PA_HOOK_OK; + + pa_log_info("A new sink has been discovered. Unloading null-sink."); + + pa_module_unload_request(u->null_module); + u->null_module = NULL; + + return PA_HOOK_OK; +} + +static pa_hook_result_t unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + + /* First check to see if it's our own null-sink that's been removed... */ + if (u->null_module && sink->module == u->null_module) { + pa_log_debug("Autoloaded null-sink removed"); + u->null_module = NULL; + return PA_HOOK_OK; + } + + load_null_sink_if_needed(c, sink, u); + + 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_name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + u->put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) put_hook_callback, u); + u->unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) unlink_hook_callback, u); + u->null_module = NULL; + u->ignore = FALSE; + + pa_modargs_free(ma); + + load_null_sink_if_needed(m->core, NULL, u); + + return 0; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->put_slot) + pa_hook_slot_free(u->put_slot); + if (u->unlink_slot) + pa_hook_slot_free(u->unlink_slot); + if (u->null_module) + pa_module_unload_request(u->null_module); + + pa_xfree(u->sink_name); + pa_xfree(u); +} diff --git a/src/modules/module-bt-proximity.c b/src/modules/module-bt-proximity.c new file mode 100644 index 00000000..77b95868 --- /dev/null +++ b/src/modules/module-bt-proximity.c @@ -0,0 +1,490 @@ +/*** + 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 index d5374838..df7783fa 100644 --- a/src/modules/module-cli.c +++ b/src/modules/module-cli.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <pulsecore/module.h> @@ -33,13 +32,15 @@ #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_USAGE("exit_on_eof=<exit daemon after EOF?>") +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", @@ -48,9 +49,9 @@ static const char* const valid_modargs[] = { static void eof_and_unload_cb(pa_cli*c, void *userdata) { pa_module *m = userdata; - - assert(c); - assert(m); + + pa_assert(c); + pa_assert(m); pa_module_unload_request(m); } @@ -58,21 +59,20 @@ static void eof_and_unload_cb(pa_cli*c, void *userdata) { static void eof_and_exit_cb(pa_cli*c, void *userdata) { pa_module *m = userdata; - assert(c); - assert(m); + pa_assert(c); + pa_assert(m); m->core->mainloop->quit(m->core->mainloop, 0); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; - int exit_on_eof = 0; - - assert(c); - assert(m); + pa_bool_t exit_on_eof = FALSE; + + pa_assert(m); - if (c->running_as_daemon) { + if (m->core->running_as_daemon) { pa_log_info("Running as daemon, refusing to load this module."); return 0; } @@ -81,7 +81,7 @@ int pa__init(pa_core *c, pa_module*m) { 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; @@ -92,17 +92,15 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - io = pa_iochannel_new(c->mainloop, STDIN_FILENO, STDOUT_FILENO); - assert(io); + io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO); pa_iochannel_set_noclose(io, 1); - m->userdata = pa_cli_new(c, io, m); - assert(m->userdata); + 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: @@ -113,11 +111,10 @@ fail: return -1; } -void pa__done(pa_core *c, pa_module*m) { - assert(c); - assert(m); +void pa__done(pa_module*m) { + pa_assert(m); - if (c->running_as_daemon == 0) { + 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 index f3bb3fd3..cef7a99a 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,12 +23,13 @@ #include <config.h> #endif -#include <assert.h> #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> @@ -38,32 +39,40 @@ #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 <pulsecore/time-smoother.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_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> ") + "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "combined" -#define MEMBLOCKQ_MAXLENGTH (1024*170) -#define RENDER_SIZE (1024*10) -#define DEFAULT_ADJUST_TIME 20 +#define MEMBLOCKQ_MAXLENGTH (1024*1024*16) + +#define DEFAULT_ADJUST_TIME 10 + +#define REQUEST_LATENCY_USEC (PA_USEC_PER_MSEC * 200) static const char* const valid_modargs[] = { "sink_name", - "master", "slaves", "adjust_time", "resample_method", @@ -76,95 +85,150 @@ static const char* const valid_modargs[] = { struct output { struct userdata *userdata; + + pa_sink *sink; pa_sink_input *sink_input; - size_t counter; + + 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_read, *inq_rtpoll_item_write; + pa_rtpoll_item *outq_rtpoll_item_read, *outq_rtpoll_item_write; + pa_memblockq *memblockq; + pa_usec_t total_latency; + + pa_atomic_t max_request; + PA_LLIST_FIELDS(struct output); }; struct userdata { - pa_module *module; pa_core *core; + pa_module *module; pa_sink *sink; - unsigned n_outputs; - struct output *master; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_time_event *time_event; uint32_t adjust_time; - - PA_LLIST_HEAD(struct output, outputs); + + pa_bool_t automatic; + + pa_hook_slot *sink_put_slot, *sink_unlink_slot, *sink_state_changed_slot; + + pa_resample_method_t resample_method; + + struct timeval adjust_timestamp; + + pa_usec_t block_usec; + + 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 */ + pa_usec_t timestamp; + pa_bool_t in_null_mode; + pa_smoother *smoother; + uint64_t counter; + } thread_info; }; -static void output_free(struct output *o); -static void clear_up(struct userdata *u); +enum { + SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_REMOVE_OUTPUT, + SINK_MESSAGE_NEED, + SINK_MESSAGE_UPDATE_LATENCY, + SINK_MESSAGE_UPDATE_MAX_REQUEST +}; -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, u->sink ? pa_sink_used_by(u->sink) : 0); -} +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 adjust_rates(struct userdata *u) { struct output *o; - pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency; + pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency, avg_total_latency = 0; uint32_t base_rate; - assert(u && u->sink); + uint32_t idx; + unsigned n = 0; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + if (pa_idxset_size(u->outputs) <= 0) + return; + + if (!PA_SINK_IS_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_IS_OPENED(pa_sink_get_state(o->sink))) + continue; + + o->total_latency = pa_sink_input_get_latency(o->sink_input, &sink_latency); + o->total_latency += sink_latency; - for (o = u->outputs; o; o = o->next) { - uint32_t sink_latency = o->sink_input->sink ? pa_sink_get_latency(o->sink_input->sink) : 0; - - 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 (o->total_latency < min_total_latency) + if (min_total_latency == (pa_usec_t) -1 || o->total_latency < min_total_latency) min_total_latency = o->total_latency; + + avg_total_latency += o->total_latency; + n++; } - assert(min_total_latency != (pa_usec_t) -1); + if (min_total_latency == (pa_usec_t) -1) + return; + + avg_total_latency /= n; 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] avg total latency is %0.2f msec.", u->sink->name, (double) avg_total_latency / PA_USEC_PER_MSEC); + pa_log_info("[%s] target latency is %0.2f msec.", u->sink->name, (double) target_latency / PA_USEC_PER_MSEC); base_rate = u->sink->sample_spec.rate; - for (o = u->outputs; o; o = o->next) { - uint32_t r = base_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_IS_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/ 1000000); + 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/ 1000000); + 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); - 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); + 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).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_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.", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), r, (double) r / base_rate, (float) o->total_latency); pa_sink_input_set_rate(o->sink_input, r); } } -} - -static void request_memblock(struct userdata *u) { - pa_memchunk chunk; - struct output *o; - assert(u && u->sink); - - update_usage(u); - - if (pa_sink_render(u->sink, RENDER_SIZE, &chunk) < 0) - return; - for (o = u->outputs; o; o = o->next) - pa_memblockq_push_align(o->memblockq, &chunk); - - pa_memblock_unref(chunk.memblock); + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UPDATE_LATENCY, NULL, (int64_t) avg_total_latency, NULL); } -static void time_callback(pa_mainloop_api*a, pa_time_event* e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { +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; - assert(u && a && u->time_event == e); + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); adjust_rates(u); @@ -173,73 +237,603 @@ static void time_callback(pa_mainloop_api*a, pa_time_event* e, PA_GCC_UNUSED con u->sink->core->mainloop->time_restart(e, &n); } -static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) { - struct output *o = i->userdata; - assert(i && o && o->sink_input && chunk); +static void process_render_null(struct userdata *u, pa_usec_t now) { + size_t ate = 0; + pa_assert(u); - if (pa_memblockq_peek(o->memblockq, chunk) >= 0) - return 0; - - /* Try harder */ - request_memblock(o->userdata); - - return pa_memblockq_peek(o->memblockq, chunk); + if (u->thread_info.in_null_mode) + u->thread_info.timestamp = now; + + while (u->thread_info.timestamp < now + u->block_usec) { + pa_memchunk chunk; + + pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); + pa_memblock_unref(chunk.memblock); + + u->thread_info.counter += chunk.length; + +/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */ + u->thread_info.timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); + + ate += chunk.length; + + if (ate >= u->sink->thread_info.max_request) + break; + } + +/* pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, (unsigned long) nbytes); */ + + pa_smoother_put(u->thread_info.smoother, now, + pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec) - (u->thread_info.timestamp - now)); +} + +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); + + u->thread_info.timestamp = pa_rtclock_usec(); + u->thread_info.in_null_mode = FALSE; + + for (;;) { + int ret; + + /* If no outputs are connected, render some data and drop it immediately. */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && !u->thread_info.active_outputs) { + pa_usec_t now; + + now = pa_rtclock_usec(); + + if (!u->thread_info.in_null_mode || u->thread_info.timestamp <= now) + process_render_null(u, now); + + 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 + * 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); + + u->thread_info.counter += chunk.length; + + /* 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_pop_cb(pa_sink_input *i, size_t nbytes, 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, nbytes); + + if (pa_memblockq_peek(o->memblockq, chunk) < 0) + return -1; + + pa_memblockq_drop(o->memblockq, chunk->length); + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert(nbytes > 0); + pa_assert_se(o = i->userdata); + + pa_memblockq_rewind(o->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + pa_memblockq_set_maxrewind(o->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + if (pa_atomic_load(&o->max_request) == (int) nbytes) + return; + + pa_atomic_store(&o->max_request, (int) nbytes); + + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); } -static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { - struct output *o = i->userdata; - assert(i && o && o->sink_input && chunk && 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); - pa_memblockq_drop(o->memblockq, chunk, length); - o->counter += length; + /* Set up the queue from the sink thread to us */ + pa_assert(!o->inq_rtpoll_item_read && !o->outq_rtpoll_item_write); + + o->inq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + i->sink->rtpoll, + PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */ + o->inq); + + o->outq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + i->sink->rtpoll, + PA_RTPOLL_EARLY, + o->outq); } +/* 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_read && o->outq_rtpoll_item_write); + + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + o->inq_rtpoll_item_read = NULL; + + pa_rtpoll_item_free(o->outq_rtpoll_item_write); + o->outq_rtpoll_item_write = NULL; +} + +/* Called from main context */ static void sink_input_kill_cb(pa_sink_input *i) { - struct output *o = i->userdata; - assert(i && o && o->sink_input); + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert(o = i->userdata); + pa_module_unload_request(o->userdata->module); - clear_up(o->userdata); + output_free(o); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); +} + +/* 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_IS_OPENED(o->sink_input->sink->thread_info.state)) + pa_memblockq_push_align(o->memblockq, chunk); + else + pa_memblockq_flush(o->memblockq); + + return 0; + } + + 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_sink_input_unlink(o->sink_input); + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + 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_IS_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); + } } -static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) { - struct output *o = i->userdata; - assert(i && o && o->sink_input); - - return pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &i->sample_spec); +/* 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); + + pa_log_info("Device suspended..."); } -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - assert(s && u && u->sink && u->master); +/* Called from main context */ +static void unsuspend(struct userdata *u) { + struct output *o; + uint32_t idx; + + pa_assert(u); - return - pa_sink_input_get_latency(u->master->sink_input) + - pa_sink_get_latency(u->master->sink_input->sink); + /* 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_IS_OPENED(pa_sink_get_state(o->sink))) + enable_output(o); + } + + pa_log_info("Resumed successfully..."); } -static void sink_notify(pa_sink *s) { +/* 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_IS_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 IO context */ +static void update_max_request(struct userdata *u) { + size_t max_request = 0; struct output *o; - - assert(s); - u = s->userdata; - assert(u); - - for (o = u->outputs; o; o = o->next) - pa_sink_notify(o->sink_input->sink); -} - -static struct output *output_new(struct userdata *u, pa_sink *sink, int resample_method) { - struct output *o = NULL; - char t[256]; + + for (o = u->thread_info.active_outputs; o; o = o->next) { + size_t mr = (size_t) pa_atomic_load(&o->max_request); + + if (mr > max_request) + max_request = mr; + } + + if (max_request <= 0) + max_request = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + + pa_sink_set_max_request(u->sink, max_request); +} + +/* Called from thread context of the io thread */ +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); + + if (PA_PTR_TO_UINT(data) == PA_SINK_SUSPENDED) + pa_smoother_pause(u->thread_info.smoother, pa_rtclock_usec()); + else + pa_smoother_resume(u->thread_info.smoother, pa_rtclock_usec()); + + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t x, y, c, *delay = data; + + x = pa_rtclock_usec(); + y = pa_smoother_get(u->thread_info.smoother, x); + + c = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); + + if (y < c) + *delay = c - y; + else + *delay = 0; + + return 0; + } + + 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_read && !op->inq_rtpoll_item_write); + + op->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + u->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + op->outq); + op->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + u->rtpoll, + PA_RTPOLL_EARLY, + op->inq); + + update_max_request(u); + return 0; + } + + case SINK_MESSAGE_REMOVE_OUTPUT: { + struct output *op = data; + + PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op); + + pa_assert(op->outq_rtpoll_item_read && op->inq_rtpoll_item_write); + + pa_rtpoll_item_free(op->outq_rtpoll_item_read); + op->outq_rtpoll_item_read = NULL; + + pa_rtpoll_item_free(op->inq_rtpoll_item_write); + op->inq_rtpoll_item_write = NULL; + + update_max_request(u); + return 0; + } + + case SINK_MESSAGE_NEED: + render_memblock(u, (struct output*) data, (size_t) offset); + return 0; + + case SINK_MESSAGE_UPDATE_LATENCY: { + pa_usec_t x, y, latency = (pa_usec_t) offset; + + x = pa_rtclock_usec(); + y = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); + + if (y > latency) + y -= latency; + else + y = 0; + + pa_smoother_put(u->thread_info.smoother, x, y); + return 0; + } + + case SINK_MESSAGE_UPDATE_MAX_REQUEST: + + update_max_request(u); + break; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void update_description(struct userdata *u) { + pa_bool_t first = TRUE; + 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, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + first = FALSE; + } else + e = pa_sprintf_malloc("%s, %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + pa_xfree(t); + t = e; + } + + pa_sink_set_description(u->sink, t); + pa_xfree(t); +} + +static int output_create_sink_input(struct output *o) { pa_sink_input_new_data data; - - assert(u && sink && u->sink); - - o = pa_xmalloc(sizeof(struct output)); + + pa_assert(o); + + if (o->sink_input) + return 0; + + pa_sink_input_new_data_init(&data); + data.sink = o->sink; + data.driver = __FILE__; + pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "Simultaneous output on %s", pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + 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_sink_input_new_data_done(&data); + + if (!o->sink_input) + return -1; + + o->sink_input->parent.process_msg = sink_input_process_msg; + o->sink_input->pop = sink_input_pop_cb; + o->sink_input->process_rewind = sink_input_process_rewind_cb; + o->sink_input->state_change = sink_input_state_change_cb; + o->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + o->sink_input->update_max_request = sink_input_update_max_request_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; + + pa_sink_input_set_requested_latency(o->sink_input, REQUEST_LATENCY_USEC); + + return 0; +} + +static struct output *output_new(struct userdata *u, pa_sink *sink) { + struct output *o; + pa_sink_state_t state; + + pa_assert(u); + pa_assert(sink); + pa_assert(u->sink); + + o = pa_xnew(struct output, 1); o->userdata = u; - - o->counter = 0; + o->inq = pa_asyncmsgq_new(0); + o->outq = pa_asyncmsgq_new(0); + o->inq_rtpoll_item_write = o->inq_rtpoll_item_read = NULL; + o->outq_rtpoll_item_write = o->outq_rtpoll_item_read = NULL; + o->sink = sink; + o->sink_input = NULL; o->memblockq = pa_memblockq_new( 0, MEMBLOCKQ_MAXLENGTH, @@ -247,92 +841,174 @@ static struct output *output_new(struct userdata *u, pa_sink *sink, int resample pa_frame_size(&u->sink->sample_spec), 1, 0, + 0, NULL); + pa_atomic_store(&o->max_request, 0); + PA_LLIST_INIT(struct output, o); + + pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0); + + state = pa_sink_get_state(u->sink); + + if (state != PA_SINK_INIT) + 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_read = pa_rtpoll_item_new_asyncmsgq_read( + u->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + o->outq); + o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + u->rtpoll, + PA_RTPOLL_EARLY, + o->inq); + } - snprintf(t, sizeof(t), "Output stream #%u of sink %s", u->n_outputs+1, u->sink->name); + if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) { + pa_sink_suspend(sink, FALSE); - 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, &u->sink->sample_spec); - pa_sink_input_new_data_set_channel_map(&data, &u->sink->channel_map); - data.module = u->module; - - if (!(o->sink_input = pa_sink_input_new(u->core, &data, PA_SINK_INPUT_VARIABLE_RATE))) - goto fail; + if (PA_SINK_IS_OPENED(pa_sink_get_state(sink))) + if (output_create_sink_input(o) < 0) + goto fail; + } + + update_description(u); - o->sink_input->get_latency = sink_input_get_latency_cb; - o->sink_input->peek = sink_input_peek_cb; - o->sink_input->drop = sink_input_drop_cb; - o->sink_input->kill = sink_input_kill_cb; - o->sink_input->userdata = o; - - PA_LLIST_PREPEND(struct output, u->outputs, o); - u->n_outputs++; return o; fail: if (o) { + pa_idxset_remove_by_data(u->outputs, o, NULL); + if (o->sink_input) { - pa_sink_input_disconnect(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 void output_free(struct output *o) { - assert(o); - PA_LLIST_REMOVE(struct output, o->userdata->outputs, o); - o->userdata->n_outputs--; - pa_memblockq_free(o->memblockq); - pa_sink_input_disconnect(o->sink_input); - pa_sink_input_unref(o->sink_input); - pa_xfree(o); +static pa_bool_t is_suitable_sink(struct userdata *u, pa_sink *s) { + const char *t; + + pa_sink_assert_ref(s); + + if (!(s->flags & PA_SINK_HARDWARE)) + return FALSE; + + if (s == u->sink) + return FALSE; + + if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS))) + if (strcmp(t, "sound")) + return FALSE; + + return TRUE; } -static void clear_up(struct userdata *u) { +static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { struct output *o; - assert(u); - - if (u->time_event) { - u->core->mainloop->time_free(u->time_event); - u->time_event = NULL; - } - - while ((o = u->outputs)) - output_free(o); - - u->master = NULL; - - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; + + pa_core_assert_ref(c); + pa_sink_assert_ref(s); + pa_assert(u); + pa_assert(u->automatic); + + if (!is_suitable_sink(u, s)) + 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); + + return PA_HOOK_OK; } -int pa__init(pa_core *c, pa_module*m) { +static struct output* find_output(struct userdata *u, pa_sink *s) { + struct output *o; + uint32_t idx; + + pa_assert(u); + pa_assert(s); + + if (u->sink == s) + return NULL; + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o->sink == s) + return o; + + return NULL; +} + +static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); + + if (!(o = find_output(u, s))) + 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; + pa_sink_state_t state; + + if (!(o = find_output(u, s))) + return PA_HOOK_OK; + + state = pa_sink_get_state(s); + + if (PA_SINK_IS_OPENED(state) && PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) + enable_output(o); + + if (state == PA_SINK_SUSPENDED && o->sink_input) + disable_output(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; - char *n = NULL; - const char*split_state; - struct timeval tv; - int resample_method = -1; + const char *slaves, *rm; + int resample_method = PA_RESAMPLER_TRIVIAL; pa_sample_spec ss; pa_channel_map map; - - assert(c && m); + struct output *o; + uint32_t idx; + pa_sink_new_data data; + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments"); @@ -345,118 +1021,236 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } } - - u = pa_xnew(struct userdata, 1); - m->userdata = u; - u->sink = NULL; - u->n_outputs = 0; - u->master = NULL; + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; u->module = m; - u->core = c; + u->sink = NULL; u->time_event = NULL; u->adjust_time = DEFAULT_ADJUST_TIME; - PA_LLIST_HEAD_INIT(struct output, u->outputs); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + 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_put_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; + u->thread_info.counter = 0; + u->thread_info.smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) { - pa_log("failed to parse adjust_time value"); - goto fail; - } - - if (!(master_name = pa_modargs_get_value(ma, "master", NULL)) || !(slaves = pa_modargs_get_value(ma, "slaves", NULL))) { - pa_log("no master or slave sinks specified"); + pa_log("Failed to parse adjust_time value"); goto fail; } - if (!(master_sink = pa_namereg_get(c, master_name, PA_NAMEREG_SINK, 1))) { - pa_log("invalid master sink '%s'", master_name); - goto fail; - } + slaves = pa_modargs_get_value(ma, "slaves", NULL); + u->automatic = !slaves; + ss = m->core->default_sample_spec; - ss = master_sink->sample_spec; - if ((pa_modargs_get_sample_spec(ma, &ss) < 0)) { - pa_log("invalid sample specification."); + if ((pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0)) { + pa_log("Invalid sample specification."); goto fail; } - if (ss.channels == master_sink->sample_spec.channels) - map = master_sink->channel_map; - else - pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT); + pa_sink_new_data_init(&data); + data.namereg_fail = FALSE; + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Simultaneous Output"); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - if ((pa_modargs_get_channel_map(ma, &map) < 0)) { - pa_log("invalid channel map."); - goto fail; - } + if (slaves) + pa_proplist_sets(data.proplist, "combine.slaves", slaves); - if (ss.channels != map.channels) { - pa_log("channel map and sample specification don't match."); - goto fail; - } - - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink"); + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink"); goto fail; } - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, "Combined Sink"); - u->sink->get_latency = sink_get_latency_cb; - u->sink->notify = sink_notify; + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state = sink_set_state; u->sink->userdata = u; - - if (!(u->master = output_new(u, master_sink, resample_method))) { - pa_log("failed to create master sink input on sink '%s'.", u->sink->name); - goto fail; - } - - split_state = NULL; - while ((n = pa_split(slaves, ",", &split_state))) { - pa_sink *slave_sink; - - if (!(slave_sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) { - pa_log("invalid slave sink '%s'", n); - goto fail; + + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + + pa_sink_set_latency_range(u->sink, REQUEST_LATENCY_USEC, REQUEST_LATENCY_USEC); + u->block_usec = u->sink->thread_info.max_latency; + + u->sink->thread_info.max_request = + pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + + if (!u->automatic) { + const char*split_state; + char *n = NULL; + pa_assert(slaves); + + /* The slaves have been specified manually */ + + 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, TRUE)) || 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; + } } - pa_xfree(n); + if (pa_idxset_size(u->outputs) <= 1) + pa_log_warn("No slave sinks specified."); - if (!output_new(u, slave_sink, resample_method)) { - pa_log("failed to create slave sink input on sink '%s'.", slave_sink->name); - goto fail; + u->sink_put_slot = NULL; + + } else { + pa_sink *s; + + /* We're in automatic mode, we add every sink that matches our needs */ + + for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) { + + if (!is_suitable_sink(u, s)) + continue; + + if (!output_new(u, s)) { + pa_log("Failed to create sink input on sink '%s'.", s->name); + goto fail; + } } + + u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_put_hook_cb, u); } - - if (u->n_outputs <= 1) - pa_log_warn("WARNING: no slave sinks specified."); + + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (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_NORMAL, (pa_hook_cb_t) sink_state_changed_hook_cb, u); + + 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 = c->mainloop->time_new(c->mainloop, &tv, time_callback, u); + u->time_event = m->core->mainloop->time_new(m->core->mainloop, &tv, time_callback, u); } - + pa_modargs_free(ma); - return 0; + + return 0; fail: - pa_xfree(n); - + if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); + return -1; } -void pa__done(pa_core *c, pa_module*m) { +static void output_free(struct output *o) { + pa_assert(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_read) + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + if (o->inq_rtpoll_item_write) + pa_rtpoll_item_free(o->inq_rtpoll_item_write); + + if (o->outq_rtpoll_item_read) + pa_rtpoll_item_free(o->outq_rtpoll_item_read); + if (o->outq_rtpoll_item_write) + pa_rtpoll_item_free(o->outq_rtpoll_item_write); + + 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; - assert(c && m); + struct output *o; + + pa_assert(m); if (!(u = m->userdata)) return; - clear_up(u); - pa_xfree(u); -} + if (u->sink_put_slot) + pa_hook_slot_free(u->sink_put_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); + if (u->thread_info.smoother) + pa_smoother_free(u->thread_info.smoother); + pa_xfree(u); +} diff --git a/src/modules/module-console-kit.c b/src/modules/module-console-kit.c new file mode 100644 index 00000000..3adee99e --- /dev/null +++ b/src/modules/module-console-kit.c @@ -0,0 +1,334 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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 "dbus-util.h" +#include "module-console-kit-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Create a client for each ConsoleKit session of this user"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + NULL +}; + +struct session { + char *id; + pa_client *client; +}; + +struct userdata { + pa_core *core; + pa_dbus_connection *connection; + pa_hashmap *sessions; +}; + +static void add_session(struct userdata *u, const char *id) { + DBusError error; + DBusMessage *m = NULL, *reply = NULL; + int32_t uid; + struct session *session; + char *t; + + dbus_error_init (&error); + + if (pa_hashmap_get(u->sessions, id)) { + pa_log_warn("Duplicate session %s, ignoring.", id); + return; + } + + if (!(m = dbus_message_new_method_call("org.freedesktop.ConsoleKit", id, "org.freedesktop.ConsoleKit.Session", "GetUnixUser"))) { + pa_log("Failed to allocate GetUnixUser() method call."); + goto fail; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { + pa_log("GetUnixUser() call failed: %s: %s", error.name, error.message); + goto fail; + } + + /* FIXME: Why is this in int32? and not an uint32? */ + if (!dbus_message_get_args(reply, &error, DBUS_TYPE_INT32, &uid, DBUS_TYPE_INVALID)) { + pa_log("Failed to parse GetUnixUser() result: %s: %s", error.name, error.message); + goto fail; + } + + /* We only care about our own sessions */ + if ((uid_t) uid != getuid()) + goto fail; + + session = pa_xnew(struct session, 1); + session->id = pa_xstrdup(id); + + t = pa_sprintf_malloc("ConsoleKit Session %s", id); + session->client = pa_client_new(u->core, __FILE__, t); + pa_xfree(t); + + pa_proplist_sets(session->client->proplist, "console-kit.session", id); + + pa_hashmap_put(u->sessions, session->id, session); + + pa_log_debug("Added new session %s", id); + +fail: + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); +} + +static void free_session(struct session *session) { + pa_assert(session); + + pa_log_debug("Removing session %s", session->id); + + pa_client_free(session->client); + pa_xfree(session->id); + pa_xfree(session); +} + +static void remove_session(struct userdata *u, const char *id) { + struct session *session; + + if (!(session = pa_hashmap_remove(u->sessions, id))) + return; + + free_session(session); +} + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { + struct userdata *u = userdata; + DBusError error; + const char *path; + + 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.ConsoleKit.Seat", "SessionAdded")) { + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + pa_log_error("Failed to parse SessionAdded message: %s: %s", error.name, error.message); + goto finish; + } + + add_session(u, path); + + } else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) { + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + pa_log_error("Failed to parse SessionRemoved message: %s: %s", error.name, error.message); + goto finish; + } + + remove_session(u, path); + } + +finish: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static int get_session_list(struct userdata *u) { + DBusError error; + DBusMessage *m = NULL, *reply = NULL; + uint32_t uid; + DBusMessageIter iter, sub; + int ret = -1; + + pa_assert(u); + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "GetSessionsForUnixUser"))) { + pa_log("Failed to allocate GetSessionsForUnixUser() method call."); + goto fail; + } + + uid = (uint32_t) getuid(); + if (!(dbus_message_append_args(m, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID))) { + pa_log("Failed to append arguments to GetSessionsForUnixUser() method call."); + goto fail; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { + pa_log("GetSessionsForUnixUser() call failed: %s: %s", error.name, error.message); + goto fail; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_OBJECT_PATH) { + pa_log("Failed to parse GetSessionsForUnixUser() result."); + goto fail; + } + + dbus_message_iter_recurse(&iter, &sub); + + for (;;) { + int at; + const char *id; + + if ((at = dbus_message_iter_get_arg_type(&sub)) == DBUS_TYPE_INVALID) + break; + + assert(at == DBUS_TYPE_OBJECT_PATH); + dbus_message_iter_get_basic(&sub, &id); + + add_session(u, id); + + dbus_message_iter_next(&sub); + } + + ret = 0; + +fail: + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return ret; +} + +int pa__init(pa_module*m) { + DBusError error; + pa_dbus_connection *connection; + struct userdata *u = NULL; + pa_modargs *ma; + + 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 (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { + + if (connection) + pa_dbus_connection_unref(connection); + + pa_log_error("Unable to contact D-Bus system bus: %s: %s", error.name, error.message); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->connection = connection; + u->sessions = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + if (!dbus_connection_add_filter(pa_dbus_connection_get(connection), filter_cb, u, NULL)) { + pa_log_error("Failed to add filter function"); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(connection), "type='signal',sender='org.freedesktop.ConsoleKit', interface='org.freedesktop.ConsoleKit.Seat'", &error); + if (dbus_error_is_set(&error)) { + pa_log_error("Unable to subscribe to ConsoleKit signals: %s: %s", error.name, error.message); + goto fail; + } + + if (get_session_list(u) < 0) + goto fail; + + 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; + struct session *session; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sessions) { + while ((session = pa_hashmap_steal_first(u->sessions))) + free_session(session); + + pa_hashmap_free(u->sessions, NULL, NULL); + } + + if (u->connection) + pa_dbus_connection_unref(u->connection); + + 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..2168ac71 --- /dev/null +++ b/src/modules/module-default-device-restore.c @@ -0,0 +1,201 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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 <pulse/timeval.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-error.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" +#define DEFAULT_SAVE_INTERVAL 5 + +struct userdata { + pa_core *core; + pa_subscription *subscription; + pa_time_event *time_event; + char *sink_filename, *source_filename; + pa_bool_t modified; +}; + +static void load(struct userdata *u) { + FILE *f; + + /* We never overwrite manually configured settings */ + + if (u->core->default_sink_name) + pa_log_info("Manually configured default sink, not overwriting."); + else if ((f = fopen(u->sink_filename, "r"))) { + char ln[256] = ""; + + fgets(ln, sizeof(ln)-1, f); + pa_strip_nl(ln); + fclose(f); + + if (!ln[0]) + pa_log_info("No previous default sink setting, ignoring."); + else if (pa_namereg_get(u->core, ln, PA_NAMEREG_SINK, TRUE)) { + pa_namereg_set_default(u->core, ln, PA_NAMEREG_SINK); + pa_log_info("Restored default sink '%s'.", ln); + } else + pa_log_info("Saved default sink '%s' not existant, not restoring default sink setting.", ln); + + } else if (errno != ENOENT) + pa_log("Failed to load default sink: %s", pa_cstrerror(errno)); + + if (u->core->default_source_name) + pa_log_info("Manually configured default source, not overwriting."); + else if ((f = fopen(u->source_filename, "r"))) { + char ln[256] = ""; + + fgets(ln, sizeof(ln)-1, f); + pa_strip_nl(ln); + fclose(f); + + if (!ln[0]) + pa_log_info("No previous default source setting, ignoring."); + else if (pa_namereg_get(u->core, ln, PA_NAMEREG_SOURCE, TRUE)) { + pa_namereg_set_default(u->core, ln, PA_NAMEREG_SOURCE); + pa_log_info("Restored default source '%s'.", ln); + } else + pa_log_info("Saved default source '%s' not existant, not restoring default source setting.", ln); + + } else if (errno != ENOENT) + pa_log("Failed to load default sink: %s", pa_cstrerror(errno)); +} + +static void save(struct userdata *u) { + FILE *f; + + if (!u->modified) + return; + + if (u->sink_filename) { + if ((f = fopen(u->sink_filename, "w"))) { + const char *n = pa_namereg_get_default_sink_name(u->core); + fprintf(f, "%s\n", pa_strempty(n)); + fclose(f); + } else + pa_log("Failed to save default sink: %s", pa_cstrerror(errno)); + } + + if (u->source_filename) { + if ((f = fopen(u->source_filename, "w"))) { + const char *n = pa_namereg_get_default_source_name(u->core); + fprintf(f, "%s\n", pa_strempty(n)); + fclose(f); + } else + pa_log("Failed to save default source: %s", pa_cstrerror(errno)); + } + + u->modified = FALSE; +} + +static void time_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + save(u); + + if (u->time_event) { + u->core->mainloop->time_free(u->time_event); + u->time_event = NULL; + } +} + +static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + u->modified = TRUE; + + if (!u->time_event) { + struct timeval tv; + pa_gettimeofday(&tv); + pa_timeval_add(&tv, DEFAULT_SAVE_INTERVAL*PA_USEC_PER_SEC); + u->time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, time_cb, u); + } +} + +int pa__init(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + + if (!(u->sink_filename = pa_state_path(DEFAULT_SINK_FILE))) + goto fail; + + if (!(u->source_filename = pa_state_path(DEFAULT_SOURCE_FILE))) + goto fail; + + load(u); + + u->subscription = pa_subscription_new(u->core, PA_SUBSCRIPTION_MASK_SERVER, subscribe_cb, u); + + 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; + + save(u); + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->time_event) + m->core->mainloop->time_free(u->time_event); + + pa_xfree(u->sink_filename); + pa_xfree(u->source_filename); + pa_xfree(u); +} diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4 index c961412d..64ce1928 100644 --- a/src/modules/module-defs.h.m4 +++ b/src/modules/module-defs.h.m4 @@ -1,4 +1,3 @@ -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 @@ -10,6 +9,7 @@ define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl #include <pulsecore/core.h> #include <pulsecore/module.h> +#include <pulsecore/macro.h> gen_symbol(pa__init) gen_symbol(pa__done) @@ -17,13 +17,15 @@ 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(struct pa_core *c, struct pa_module*m); -void pa__done(struct pa_core *c, struct pa_module*m); +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 index 84ccd14c..13bcfcd1 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -1,18 +1,20 @@ -/* $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 @@ -24,7 +26,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> #include <stdlib.h> @@ -40,13 +41,20 @@ #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_USAGE("just-one=<boolean>") +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 @@ -58,7 +66,7 @@ static int detect_alsa(pa_core *c, int just_one) { if (errno != ENOENT) pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno)); - + return -1; } @@ -66,7 +74,7 @@ static int detect_alsa(pa_core *c, int just_one) { char line[64], args[64]; unsigned device, subdevice; int is_sink; - + if (!fgets(line, sizeof(line), f)) break; @@ -81,7 +89,7 @@ static int detect_alsa(pa_core *c, int just_one) { if (just_one && is_sink && n_sink >= 1) continue; - + if (just_one && !is_sink && n_source >= 1) continue; @@ -92,7 +100,7 @@ static int detect_alsa(pa_core *c, int just_one) { if (subdevice != 0) continue; - snprintf(args, sizeof(args), "device=hw:%u", device); + pa_snprintf(args, sizeof(args), "device=hw:%u", device); if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args)) continue; @@ -105,7 +113,7 @@ static int detect_alsa(pa_core *c, int just_one) { } fclose(f); - + return n; } #endif @@ -114,7 +122,7 @@ static int detect_alsa(pa_core *c, int just_one) { 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"))) { @@ -128,36 +136,36 @@ static int detect_oss(pa_core *c, int just_one) { 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; + 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) - snprintf(args, sizeof(args), "device=/dev/dsp"); + pa_snprintf(args, sizeof(args), "device=/dev/dsp"); else - snprintf(args, sizeof(args), "device=/dev/dsp%u", device); - + 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) { + + } else if (sscanf(line, "pcm%u: ", &device) == 1) { /* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */ - snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device); - + pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device); + if (!pa_module_load(c, "module-oss", args)) continue; - } + } n++; @@ -189,7 +197,7 @@ static int detect_solaris(pa_core *c, int just_one) { if (!S_ISCHR(s.st_mode)) return 0; - snprintf(args, sizeof(args), "device=%s", dev); + pa_snprintf(args, sizeof(args), "device=%s", dev); if (!pa_module_load(c, "module-solaris", args)) return 0; @@ -211,39 +219,34 @@ static int detect_waveout(pa_core *c, int just_one) { } #endif -int pa__init(pa_core *c, pa_module*m) { - int just_one = 0, n = 0; +int pa__init(pa_module*m) { + pa_bool_t just_one = FALSE; + int n = 0; pa_modargs *ma; - static const char* const valid_modargs[] = { - "just-one", - NULL - }; - - assert(c); - assert(m); + 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(c, just_one)) <= 0) + if ((n = detect_alsa(m->core, just_one)) <= 0) #endif #if HAVE_OSS - if ((n = detect_oss(c, just_one)) <= 0) + if ((n = detect_oss(m->core, just_one)) <= 0) #endif #if HAVE_SOLARIS - if ((n = detect_solaris(c, just_one)) <= 0) + if ((n = detect_solaris(m->core, just_one)) <= 0) #endif #if OS_IS_WIN32 - if ((n = detect_waveout(c, just_one)) <= 0) + if ((n = detect_waveout(m->core, just_one)) <= 0) #endif { pa_log_warn("failed to detect any sound hardware."); @@ -251,7 +254,7 @@ int pa__init(pa_core *c, pa_module*m) { } pa_log_info("loaded %i modules.", n); - + /* We were successful and can unload ourselves now. */ pa_module_unload_request(m); @@ -262,12 +265,6 @@ int pa__init(pa_core *c, pa_module*m) { fail: if (ma) pa_modargs_free(ma); - - return -1; -} - -void pa__done(PA_GCC_UNUSED pa_core *c, PA_GCC_UNUSED pa_module*m) { - /* NOP */ + return -1; } - diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c new file mode 100644 index 00000000..b7a1e1b5 --- /dev/null +++ b/src/modules/module-device-restore.c @@ -0,0 +1,348 @@ +/*** + 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 <gdbm.h> + +#include <pulse/xmalloc.h> +#include <pulse/volume.h> +#include <pulse/timeval.h> +#include <pulse/util.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-device-restore-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically restore the volume/mute state of devices"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define SAVE_INTERVAL 10 + +static const char* const valid_modargs[] = { + NULL, +}; + +struct userdata { + pa_core *core; + pa_subscription *subscription; + pa_hook_slot *sink_fixate_hook_slot, *source_fixate_hook_slot; + pa_time_event *save_time_event; + GDBM_FILE gdbm_file; +}; + +struct entry { + pa_cvolume volume; + int muted; +}; + +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; + + gdbm_sync(u->gdbm_file); + pa_log_info("Synced."); +} + +static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + struct entry entry; + char *name; + datum key, data; + + pa_assert(c); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + pa_sink *sink; + + if (!(sink = pa_idxset_get_by_index(c->sinks, idx))) + return; + + name = pa_sprintf_malloc("sink:%s", sink->name); + entry.volume = *pa_sink_get_volume(sink); + entry.muted = pa_sink_get_mute(sink); + + } else { + pa_source *source; + + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + + if (!(source = pa_idxset_get_by_index(c->sources, idx))) + return; + + name = pa_sprintf_malloc("source:%s", source->name); + entry.volume = *pa_source_get_volume(source); + entry.muted = pa_source_get_mute(source); + } + + key.dptr = name; + key.dsize = strlen(name); + + data = gdbm_fetch(u->gdbm_file, key); + + if (data.dptr) { + + if (data.dsize == sizeof(struct entry)) { + struct entry *old = (struct entry*) data.dptr; + + if (pa_cvolume_valid(&old->volume)) { + + if (pa_cvolume_equal(&old->volume, &entry.volume) && + !old->muted == !entry.muted) { + + pa_xfree(data.dptr); + pa_xfree(name); + return; + } + } else + pa_log_warn("Invalid volume stored in database for device %s", name); + + } else + pa_log_warn("Database contains entry for device %s of wrong size %lu != %lu", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + + pa_xfree(data.dptr); + } + + data.dptr = (void*) &entry; + data.dsize = sizeof(entry); + + pa_log_info("Storing volume/mute for device %s.", name); + + gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + + if (!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); + } + + pa_xfree(name); +} + +static struct entry* read_entry(struct userdata *u, char *name) { + datum key, data; + struct entry *e; + + pa_assert(u); + pa_assert(name); + + key.dptr = name; + key.dsize = strlen(name); + + data = gdbm_fetch(u->gdbm_file, key); + + if (!data.dptr) + goto fail; + + if (data.dsize != sizeof(struct entry)) { + pa_log_warn("Database contains entry for device %s of wrong size %lu != %lu", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + goto fail; + } + + e = (struct entry*) data.dptr; + + if (!(pa_cvolume_valid(&e->volume))) { + pa_log_warn("Invalid volume stored in database for device %s", name); + goto fail; + } + + return e; + +fail: + + pa_xfree(data.dptr); + return NULL; +} + + +static pa_hook_result_t sink_fixate_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(new_data); + + name = pa_sprintf_malloc("sink:%s", new_data->name); + + if ((e = read_entry(u, name))) { + + if (e->volume.channels == new_data->sample_spec.channels) { + pa_log_info("Restoring volume for sink %s.", new_data->name); + pa_sink_new_data_set_volume(new_data, &e->volume); + } + + pa_log_info("Restoring mute state for sink %s.", new_data->name); + pa_sink_new_data_set_muted(new_data, e->muted); + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(new_data); + + name = pa_sprintf_malloc("source:%s", new_data->name); + + if ((e = read_entry(u, name))) { + + if (e->volume.channels == new_data->sample_spec.channels) { + pa_log_info("Restoring volume for source %s.", new_data->name); + pa_source_new_data_set_volume(new_data, &e->volume); + } + + pa_log_info("Restoring mute state for source %s.", new_data->name); + pa_source_new_data_set_muted(new_data, e->muted); + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + char *fname, *fn; + char hn[256]; + pa_sink *sink; + pa_source *source; + uint32_t idx; + + 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->save_time_event = NULL; + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u); + + u->sink_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_fixate_hook_callback, u); + u->source_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_fixate_hook_callback, u); + + m->userdata = u; + + if (!pa_get_host_name(hn, sizeof(hn))) + goto fail; + + fn = pa_sprintf_malloc("device-volumes.%s.gdbm", hn); + fname = pa_state_path(fn); + pa_xfree(fn); + + if (!fname) + goto fail; + + if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT, 0600, NULL))) { + pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + pa_xfree(fname); + goto fail; + } + + pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_xfree(fname); + + for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx)) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); + + for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx)) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, 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; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->sink_fixate_hook_slot) + pa_hook_slot_free(u->sink_fixate_hook_slot); + if (u->source_fixate_hook_slot) + pa_hook_slot_free(u->source_fixate_hook_slot); + + if (u->save_time_event) + u->core->mainloop->time_free(u->save_time_event); + + if (u->gdbm_file) + gdbm_close(u->gdbm_file); + + pa_xfree(u); +} diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c index 263e81f9..8eb4985b 100644 --- a/src/modules/module-esound-compat-spawnfd.c +++ b/src/modules/module-esound-compat-spawnfd.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> @@ -36,33 +35,36 @@ #include "module-esound-compat-spawnfd-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation") -PA_MODULE_USAGE("fd=<file descriptor>") -PA_MODULE_VERSION(PACKAGE_VERSION) +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_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1, fd = -1; char x = 1; - assert(c && m); + + 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("WARNING: write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno)); + pa_log_warn("write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno)); - close(fd); + pa_assert_se(pa_close(fd) == 0); pa_module_unload_request(m); @@ -74,9 +76,3 @@ finish: return ret; } - -void pa__done(pa_core *c, pa_module*m) { - assert(c && m); -} - - diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c index 7a662c2d..67f0a231 100644 --- a/src/modules/module-esound-compat-spawnpid.c +++ b/src/modules/module-esound-compat-spawnpid.c @@ -1,17 +1,19 @@ /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,7 +25,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <signal.h> @@ -36,21 +37,23 @@ #include "module-esound-compat-spawnpid-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation") -PA_MODULE_USAGE("pid=<process id>") -PA_MODULE_VERSION(PACKAGE_VERSION) +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_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1; uint32_t pid = 0; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs)) || pa_modargs_get_value_u32(ma, "pid", &pid) < 0 || @@ -60,7 +63,7 @@ int pa__init(pa_core *c, pa_module*m) { } if (kill(pid, SIGUSR1) < 0) - pa_log("WARNING: kill(%u) failed: %s", pid, pa_cstrerror(errno)); + pa_log_warn("kill(%u) failed: %s", pid, pa_cstrerror(errno)); pa_module_unload_request(m); @@ -72,9 +75,3 @@ finish: return ret; } - -void pa__done(pa_core *c, pa_module*m) { - assert(c && m); -} - - diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index ca1f16ce..e189febd 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -26,14 +26,23 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.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> @@ -45,40 +54,67 @@ #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_USAGE("sink_name=<name for the sink> server=<address> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate>") +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_output" +#define DEFAULT_SINK_NAME "esound_out" struct userdata { pa_core *core; - + pa_module *module; pa_sink *sink; - pa_iochannel *io; - pa_socket_client *client; - pa_defer_event *defer_event; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + pa_thread *thread; pa_memchunk memchunk; - pa_module *module; void *write_data; size_t write_length, write_index; - + void *read_data; size_t read_length, read_index; - enum { STATE_AUTH, STATE_LATENCY, STATE_RUNNING, STATE_DEAD } state; + 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[] = { @@ -91,42 +127,211 @@ static const char* const valid_modargs[] = { NULL }; -static void cancel(struct userdata *u) { - assert(u); +enum { + SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX +}; - u->state = STATE_DEAD; +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; - if (u->io) { - pa_iochannel_free(u->io); - u->io = NULL; - } + switch (code) { - if (u->defer_event) { - u->core->mainloop->defer_free(u->defer_event); - u->defer_event = NULL; - } + case PA_SINK_MESSAGE_SET_STATE: - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_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; + } } - if (u->module) { - pa_module_unload_request(u->module); - u->module = NULL; + 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_IS_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_IS_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; - assert(u); + pa_assert(u); if (!pa_iochannel_is_writable(u->io)) return 0; if (u->write_data) { - assert(u->write_index < u->write_length); + 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)); @@ -134,52 +339,44 @@ static int do_write(struct userdata *u) { } u->write_index += r; - assert(u->write_index <= u->write_length); - + pa_assert(u->write_index <= u->write_length); + if (u->write_index == u->write_length) { - free(u->write_data); + pa_xfree(u->write_data); u->write_data = NULL; u->write_index = u->write_length = 0; } - } else if (u->state == STATE_RUNNING) { - void *p; - - pa_module_set_used(u->module, pa_sink_used_by(u->sink)); - - if (!u->memchunk.length) - if (pa_sink_render(u->sink, 8192, &u->memchunk) < 0) - return 0; - - assert(u->memchunk.memblock); - assert(u->memchunk.length); - - p = pa_memblock_acquire(u->memchunk.memblock); - - if ((r = pa_iochannel_write(u->io, (uint8_t*) p + u->memchunk.index, u->memchunk.length)) < 0) { - pa_memblock_release(u->memchunk.memblock); - pa_log("write() failed: %s", pa_cstrerror(errno)); - return -1; - } - pa_memblock_release(u->memchunk.memblock); - - u->memchunk.index += r; - u->memchunk.length -= r; - - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - } } - + + 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) { - assert(u); + pa_assert(u); switch (u->state) { + case STATE_AUTH: - assert(u->read_length == sizeof(int32_t)); + pa_assert(u->read_length == sizeof(int32_t)); /* Process auth data */ if (!*(int32_t*) u->read_data) { @@ -188,32 +385,32 @@ static int handle_response(struct userdata *u) { } /* Request latency data */ - assert(!u->write_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 */ - assert(u->read_length >= sizeof(int32_t)); + 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; - assert(u->read_length == sizeof(int32_t)); + 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("WARNING! Invalid latency information received from server"); + pa_log_warn("Invalid latency information received from server"); u->latency = 0; } /* Create stream */ - assert(!u->write_data); + 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; @@ -221,45 +418,44 @@ static int handle_response(struct userdata *u) { pa_strlcpy((char*) p, "PulseAudio Tunnel", ESD_NAME_MAX); u->write_index = 0; - u->state = STATE_RUNNING; + 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: - abort(); + pa_assert_not_reached(); } return 0; } static int do_read(struct userdata *u) { - assert(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; - - assert(u->read_index < u->read_length); - + + 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"); - cancel(u); return -1; } u->read_index += r; - assert(u->read_index <= u->read_length); + pa_assert(u->read_index <= u->read_length); if (u->read_index == u->read_length) return handle_response(u); @@ -268,42 +464,19 @@ static int do_read(struct userdata *u) { return 0; } -static void do_work(struct userdata *u) { - assert(u); - - u->core->mainloop->defer_enable(u->defer_event, 0); - - if (do_read(u) < 0 || do_write(u) < 0) - cancel(u); -} - -static void notify_cb(pa_sink*s) { - struct userdata *u = s->userdata; - assert(s && u); - - if (pa_iochannel_is_writable(u->io)) - u->core->mainloop->defer_enable(u->defer_event, 1); -} - -static pa_usec_t get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - assert(s && u); +static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { + struct userdata *u = userdata; + pa_assert(u); - return - u->latency + - (u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0); -} + if (do_read(u) < 0 || do_write(u) < 0) { -static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) { - struct userdata *u = userdata; - assert(u); - do_work(u); -} + if (u->io) { + pa_iochannel_free(u->io); + u->io = NULL; + } -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_work(u); + pa_module_unload_request(u->module); + } } static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) { @@ -311,32 +484,36 @@ static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, vo pa_socket_client_unref(u->client); u->client = NULL; - + if (!io) { - pa_log("connection failed: %s", pa_cstrerror(errno)); - cancel(u); + 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_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u = NULL; - const char *p; pa_sample_spec ss; pa_modargs *ma = NULL; - char *t; - - assert(c && m); - + const char *espeaker; + uint32_t key; + pa_sink_new_data data; + + pa_assert(m); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments"); goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec(ma, &ss) < 0) { pa_log("invalid sample format specification"); goto fail; @@ -347,93 +524,142 @@ int pa__init(pa_core *c, pa_module*m) { pa_log("esound sample type support is limited to mono/stereo and U8 or S16NE sample data"); goto fail; } - - u = pa_xmalloc0(sizeof(struct userdata)); - u->core = c; + + 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, 10); + pa_memchunk_reset(&u->memchunk); + u->offset = 0; + + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + 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->sink = NULL; - u->client = NULL; - u->io = NULL; + 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(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { - pa_log("failed to create sink."); + if (!(espeaker = getenv("ESPEAKER"))) + espeaker = ESD_UNIX_SOCKET_NAME; + + espeaker = pa_modargs_get_value(ma, "server", espeaker); + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, espeaker); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Esound sink '%s'", espeaker); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); goto fail; } - if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", ESD_UNIX_SOCKET_NAME), ESD_DEFAULT_PORT))) { - pa_log("failed to connect to server."); + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + if (!(u->client = pa_socket_client_new_string(u->core->mainloop, espeaker, ESD_DEFAULT_PORT))) { + pa_log("Failed to connect to server."); goto fail; } + 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"); + pa_log("Failed to load cookie"); goto fail; } - *(int32_t*) ((uint8_t*) u->write_data + ESD_KEY_LEN) = ESD_ENDIAN_KEY; + + 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)); - - u->sink->notify = notify_cb; - u->sink->get_latency = get_latency_cb; - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p)); - pa_xfree(t); - u->memchunk.memblock = NULL; - u->memchunk.length = 0; + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } - u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u); - c->mainloop->defer_enable(u->defer_event, 0); + pa_sink_put(u->sink); - pa_modargs_free(ma); - + return 0; fail: if (ma) pa_modargs_free(ma); - - pa__done(c, m); + + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + pa_assert(m); if (!(u = m->userdata)) return; - u->module = NULL; - cancel(u); - + 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); - pa_xfree(u); -} - + 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 index 8232cd38..19430a3d 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -1,22 +1,23 @@ -/* $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. + 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 @@ -24,7 +25,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> #include <stdlib.h> @@ -42,46 +42,40 @@ #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) - -typedef enum { -#ifdef HAVE_ALSA - CAP_ALSA, +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 -#ifdef HAVE_OSS - CAP_OSS, -#endif - CAP_MAX -} capability_t; - -static const char* const capabilities[CAP_MAX] = { -#ifdef HAVE_ALSA - [CAP_ALSA] = "alsa", -#endif -#ifdef HAVE_OSS - [CAP_OSS] = "oss", -#endif -}; struct device { uint32_t index; char *udi; + char *sink_name, *source_name; + int acl_race_fix; }; struct userdata { pa_core *core; - LibHalContext *ctx; - capability_t capability; - pa_dbus_connection *conn; + LibHalContext *context; + pa_dbus_connection *connection; pa_hashmap *devices; + const char *capability; }; struct timerdata { @@ -89,23 +83,30 @@ struct timerdata { char *udi; }; -static const char* get_capability_name(capability_t cap) { - if (cap >= CAP_MAX) - return NULL; - return capabilities[cap]; -} +#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((struct device*) d); + hal_device_free(d); } static const char *strip_udi(const char *udi) { const char *slash; + if ((slash = strrchr(udi, '/'))) return slash+1; @@ -113,6 +114,7 @@ static const char *strip_udi(const char *udi) { } #ifdef HAVE_ALSA + typedef enum { ALSA_TYPE_SINK, ALSA_TYPE_SOURCE, @@ -120,227 +122,297 @@ typedef enum { ALSA_TYPE_MAX } alsa_type_t; -static alsa_type_t hal_device_get_alsa_type(LibHalContext *ctx, const char *udi, - DBusError *error) -{ +static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char *udi, DBusError *error) { char *type; alsa_type_t t; - type = libhal_device_get_property_string(ctx, udi, "alsa.type", error); - if (!type || dbus_error_is_set(error)) - return FALSE; + if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", error))) + return ALSA_TYPE_OTHER; - if (!strcmp(type, "playback")) { + if (!strcmp(type, "playback")) t = ALSA_TYPE_SINK; - } else if (!strcmp(type, "capture")) { + else if (!strcmp(type, "capture")) t = ALSA_TYPE_SOURCE; - } else { + else t = ALSA_TYPE_OTHER; - } + libhal_free_string(type); return t; } -static int hal_device_get_alsa_card(LibHalContext *ctx, const char *udi, - DBusError *error) -{ - return libhal_device_get_property_int(ctx, udi, "alsa.card", error); -} +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); -static int hal_device_get_alsa_device(LibHalContext *ctx, const char *udi, - DBusError *error) -{ - return libhal_device_get_property_int(ctx, udi, "alsa.device", error); + return r; } -static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, - DBusError *error) -{ - char args[128]; +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; - type = hal_device_get_alsa_type(u->ctx, udi, error); - if (dbus_error_is_set(error) || type == ALSA_TYPE_OTHER) - return NULL; + dbus_error_init(&error); - device = hal_device_get_alsa_device(u->ctx, udi, error); - if (dbus_error_is_set(error) || device != 0) - return NULL; + pa_assert(u); + pa_assert(sink_name); + pa_assert(source_name); - card = hal_device_get_alsa_card(u->ctx, udi, error); - if (dbus_error_is_set(error)) - return NULL; + *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"; - snprintf(args, sizeof(args), "device=hw:%u sink_name=alsa_output.%s", card, strip_udi(udi)); + 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"; - snprintf(args, sizeof(args), "device=hw:%u source_name=alsa_input.%s", card, strip_udi(udi)); + 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 pa_module_load(u->core, module_name, args); + + 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 dbus_bool_t hal_device_is_oss_pcm(LibHalContext *ctx, const char *udi, - DBusError *error) -{ - dbus_bool_t rv = FALSE; - char* type, *device_file = NULL; + +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; - type = libhal_device_get_property_string(ctx, udi, "oss.type", error); - if (!type || dbus_error_is_set(error)) - return FALSE; - - if (!strcmp(type, "pcm")) { - char *e; - - device = libhal_device_get_property_int(ctx, udi, "oss.device", error); - if (dbus_error_is_set(error) || device != 0) - goto exit; - - device_file = libhal_device_get_property_string(ctx, udi, "oss.device_file", - error); - if (!device_file || dbus_error_is_set(error)) - goto exit; - - /* hack to ignore /dev/audio style devices */ - if ((e = strrchr(device_file, '/'))) - rv = !pa_startswith(e + 1, "audio"); - } + class = libhal_device_get_property_string(context, udi, "oss.type", error); + if (dbus_error_is_set(error) || !class) + goto finish; -exit: - libhal_free_string(type); - libhal_free_string(device_file); - return rv; + 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, - DBusError *error) -{ - char args[256]; +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; - if (!hal_device_is_oss_pcm(u->ctx, udi, error) || dbus_error_is_set(error)) - return NULL; + dbus_error_init(&error); - device = libhal_device_get_property_string(u->ctx, udi, "oss.device_file", - error); - if (!device || dbus_error_is_set(error)) - return NULL; + 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; - snprintf(args, sizeof(args), "device=%s sink_name=oss_output.%s source_name=oss_input.%s", device, strip_udi(udi), strip_udi(udi)); + 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); - return pa_module_load(u->core, "module-oss", args); + 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 dbus_bool_t hal_device_add(struct userdata *u, const char *udi, - DBusError *error) -{ - pa_module* m; +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)); - switch(u->capability) { #ifdef HAVE_ALSA - case CAP_ALSA: - m = hal_device_load_alsa(u, udi, error); - break; + if (strcmp(u->capability, CAPABILITY_ALSA) == 0) + m = hal_device_load_alsa(u, udi, &sink_name, &source_name); #endif #ifdef HAVE_OSS - case CAP_OSS: - m = hal_device_load_oss(u, udi, error); - break; + if (strcmp(u->capability, CAPABILITY_OSS) == 0) + m = hal_device_load_oss(u, udi, &sink_name, &source_name); #endif - default: - assert(FALSE); /* never reached */ - break; - } - if (!m || dbus_error_is_set(error)) - return FALSE; + 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 TRUE; + return d; } -static int hal_device_add_all(struct userdata *u, capability_t capability) -{ +static int hal_device_add_all(struct userdata *u, const char *capability) { DBusError error; - int i,n,count; - dbus_bool_t r; + int i, n, count = 0; char** udis; - const char* cap = get_capability_name(capability); - assert(capability < CAP_MAX); + pa_assert(u); - pa_log_info("Trying capability %u (%s)", capability, cap); dbus_error_init(&error); - udis = libhal_find_device_by_capability(u->ctx, cap, &n, &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); + pa_log_error("Error finding devices: %s: %s", error.name, error.message); dbus_error_free(&error); return -1; } - count = 0; - u->capability = capability; - for (i = 0; i < n; ++i) { - r = hal_device_add(u, udis[i], &error); - if (dbus_error_is_set(&error)) { - pa_log_error("Error adding device: %s: %s", error.name, - error.message); - dbus_error_free(&error); - count = -1; - break; + + 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, FALSE, PA_VOLUME_NORM, NULL, NULL); + count++; + } } - if (r) - ++count; } libhal_free_string_array(udis); return count; } -static dbus_bool_t device_has_capability(LibHalContext *ctx, const char *udi, - const char* cap, DBusError *error) -{ +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(ctx, udi, "info.capabilities", - error); + + 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(ctx, udi, cap, error); + 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) -{ +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 = (struct timerdata*) userdata; + struct timerdata *td = userdata; dbus_error_init(&error); - if (libhal_device_exists(td->u->ctx, td->udi, &error)) - hal_device_add(td->u, 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); + 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, FALSE, PA_VOLUME_NORM, NULL, NULL); + } + } } pa_xfree(td->udi); @@ -348,28 +420,68 @@ static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, ea->time_free(ev); } -static void device_added_cb(LibHalContext *ctx, const char *udi) -{ +static void device_added_cb(LibHalContext *context, const char *udi) { DBusError error; struct timeval tv; - dbus_bool_t has_cap; struct timerdata *t; - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); - const char* cap = get_capability_name(u->capability); + 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); - has_cap = device_has_capability(ctx, udi, cap, &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 (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 } - /* skip it */ - if (!has_cap) + if (!good) return; /* actually add the device 1/2 second later */ @@ -379,179 +491,359 @@ static void device_added_cb(LibHalContext *ctx, const char *udi) pa_gettimeofday(&tv); pa_timeval_add(&tv, 500000); - u->core->mainloop->time_new(u->core->mainloop, &tv, - device_added_time_cb, t); + u->core->mainloop->time_new(u->core->mainloop, &tv, device_added_time_cb, t); } -static void device_removed_cb(LibHalContext* ctx, const char *udi) -{ +static void device_removed_cb(LibHalContext* context, const char *udi) { struct device *d; - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); + 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 *ctx, const char *udi, - const char* capability) -{ - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); - const char* capname = get_capability_name(u->capability); +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 (capname && !strcmp(capname, capability)) { + if (!u->capability || strcmp(u->capability, capability) == 0) /* capability we care about, pretend it's a new device */ - device_added_cb(ctx, udi); - } + device_added_cb(context, udi); } -static void lost_capability_cb(LibHalContext *ctx, const char *udi, - const char* capability) -{ - struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx); - const char* capname = get_capability_name(u->capability); +static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) { + struct userdata *u; - if (capname && !strcmp(capname, capability)) { - /* capability we care about, pretend it was removed */ - device_removed_cb(ctx, udi); - } -} + pa_assert_se(u = libhal_ctx_get_user_data(context)); -#if 0 -static void property_modified_cb(LibHalContext *ctx, const char *udi, - const char* key, - dbus_bool_t is_removed, - dbus_bool_t is_added) -{ + if (u->capability && strcmp(u->capability, capability) == 0) + /* capability we care about, pretend it was removed */ + device_removed_cb(context, udi); } -#endif -static void pa_hal_context_free(LibHalContext* hal_ctx) -{ +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); - libhal_ctx_shutdown(hal_ctx, &error); - libhal_ctx_free(hal_ctx); - if (dbus_error_is_set(&error)) { - dbus_error_free(&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, FALSE, PA_VOLUME_NORM, NULL, NULL); + 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, FALSE, PA_VOLUME_NORM, NULL, NULL); + } + } + } + + 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 userdata_free(struct userdata *u) { - pa_hal_context_free(u->ctx); - /* free the devices with the hashmap */ - pa_hashmap_free(u->devices, hal_device_free_cb, NULL); - pa_dbus_connection_unref(u->conn); - pa_xfree(u); +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* pa_hal_context_new(pa_core* c, DBusConnection *conn) -{ +static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) { DBusError error; - LibHalContext *hal_ctx = NULL; + LibHalContext *hal_context = NULL; dbus_error_init(&error); - if (!(hal_ctx = libhal_ctx_new())) { + + if (!(hal_context = libhal_ctx_new())) { pa_log_error("libhal_ctx_new() failed"); goto fail; } - if (!libhal_ctx_set_dbus_connection(hal_ctx, conn)) { - pa_log_error("Error establishing DBUS connection: %s: %s", - error.name, error.message); + 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_ctx, &error)) { - pa_log_error("Couldn't connect to hald: %s: %s", - error.name, error.message); + 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_ctx; + return hal_context; fail: - if (hal_ctx) - pa_hal_context_free(hal_ctx); + if (hal_context) + hal_context_free(hal_context); - if (dbus_error_is_set(&error)) - dbus_error_free(&error); + dbus_error_free(&error); return NULL; } -int pa__init(pa_core *c, pa_module*m) { - int n; +int pa__init(pa_module*m) { DBusError error; pa_dbus_connection *conn; struct userdata *u = NULL; - LibHalContext *hal_ctx = NULL; + LibHalContext *hal_context = NULL; + int n = 0; + pa_modargs *ma; + const char *api; - assert(c); - assert(m); + pa_assert(m); dbus_error_init(&error); - if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error))) { - pa_log_error("Unable to contact DBUS system bus: %s: %s", - error.name, error.message); - dbus_error_free(&error); - return -1; + + 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 (!(hal_ctx = pa_hal_context_new(c, pa_dbus_connection_get(conn)))) { + 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 */ - return -1; + pa_dbus_connection_unref(conn); + goto fail; } u = pa_xnew(struct userdata, 1); - u->core = c; - u->ctx = hal_ctx; - u->conn = conn; - u->devices = pa_hashmap_new(pa_idxset_string_hash_func, - pa_idxset_string_compare_func); - m->userdata = (void*) u; + 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 - if ((n = hal_device_add_all(u, CAP_ALSA)) <= 0) + n = hal_device_add_all(u, CAPABILITY_ALSA); +#endif +#if defined(HAVE_ALSA) && defined(HAVE_OSS) + if (n <= 0) #endif #ifdef HAVE_OSS - if ((n = hal_device_add_all(u, CAP_OSS)) <= 0) + n += hal_device_add_all(u, CAPABILITY_OSS); #endif - { - pa_log_warn("failed to detect any sound hardware."); - userdata_free(u); - return -1; + + 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; } - libhal_ctx_set_user_data(hal_ctx, (void*) u); - libhal_ctx_set_device_added(hal_ctx, device_added_cb); - libhal_ctx_set_device_removed(hal_ctx, device_removed_cb); - libhal_ctx_set_device_new_capability(hal_ctx, new_capability_cb); - libhal_ctx_set_device_lost_capability(hal_ctx, lost_capability_cb); - /*libhal_ctx_set_device_property_modified(hal_ctx, property_modified_cb);*/ + 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_error_init(&error); - if (!libhal_device_property_watch_all(hal_ctx, &error)) { - pa_log_error("error monitoring device list: %s: %s", - error.name, error.message); - dbus_error_free(&error); - userdata_free(u); - return -1; + 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; } - pa_log_info("loaded %i modules.", n); + 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_GCC_UNUSED pa_core *c, pa_module *m) { - assert (c && m); +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); - /* free the user data */ - userdata_free(m->userdata); + 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 index 66ded27f..c4d47f8e 100644 --- a/src/modules/module-jack-sink.c +++ b/src/modules/module-jack-sink.c @@ -1,18 +1,18 @@ -/* $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 @@ -26,46 +26,59 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> -#include <pthread.h> #include <jack/jack.h> #include <pulse/xmalloc.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 <pulse/mainloop-api.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" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Jack Sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +/* 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>") + "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "jack_out" struct userdata { pa_core *core; pa_module *module; - pa_sink *sink; unsigned channels; @@ -73,19 +86,18 @@ struct userdata { jack_port_t* port[PA_CHANNELS_MAX]; jack_client_t *client; - pthread_mutex_t mutex; - pthread_cond_t cond; - - void * buffer[PA_CHANNELS_MAX]; - jack_nframes_t frames_requested; - int quit_requested; + void *buffer[PA_CHANNELS_MAX]; - int pipe_fd_type; - int pipe_fds[2]; - pa_io_event *io_event; + 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 timestamp; + jack_nframes_t saved_frame_time; + pa_bool_t saved_frame_time_valid; }; static const char* const valid_modargs[] = { @@ -98,146 +110,160 @@ static const char* const valid_modargs[] = { NULL }; -static void stop_sink(struct userdata *u) { - assert (u); - - jack_client_close(u->client); - u->client = NULL; - u->core->mainloop->io_free(u->io_event); - u->io_event = NULL; - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - pa_module_unload_request(u->module); -} +enum { + SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_ON_SHUTDOWN +}; -static void io_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { - struct userdata *u = userdata; - char x; - - assert(m); - assert(e); - assert(flags == PA_IO_EVENT_INPUT); - assert(u); - assert(u->pipe_fds[0] == fd); - - pa_read(fd, &x, 1, &u->pipe_fd_type); - - if (u->quit_requested) { - stop_sink(u); - u->quit_requested = 0; - return; - } - - pthread_mutex_lock(&u->mutex); - - if (u->frames_requested > 0) { - unsigned fs; - jack_nframes_t frame_idx; - pa_memchunk chunk; - void *p; - - fs = pa_frame_size(&u->sink->sample_spec); - - pa_sink_render_full(u->sink, u->frames_requested * fs, &chunk); - p = pa_memblock_acquire(chunk.memblock); - - for (frame_idx = 0; frame_idx < u->frames_requested; frame_idx ++) { - unsigned c; - - for (c = 0; c < u->channels; c++) { - float *s = ((float*) ((uint8_t*) p + chunk.index)) + (frame_idx * u->channels) + c; - float *d = ((float*) u->buffer[c]) + frame_idx; - - *d = *s; +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); } - } - - pa_memblock_release(chunk.memblock); - pa_memblock_unref(chunk.memblock); - u->frames_requested = 0; - - pthread_cond_signal(&u->cond); - } + u->frames_in_buffer = offset; + u->saved_frame_time = * (jack_nframes_t*) data; + u->saved_frame_time_valid = TRUE; - pthread_mutex_unlock(&u->mutex); -} + return 0; -static void request_render(struct userdata *u) { - char c = 'x'; - assert(u); + 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; - assert(u->pipe_fds[1] >= 0); - pa_write(u->pipe_fds[1], &c, 1, &u->pipe_fd_type); -} + case PA_SINK_MESSAGE_GET_LATENCY: { + jack_nframes_t l, ft, d; + size_t n; -static void jack_shutdown(void *arg) { - struct userdata *u = arg; - assert(u); + /* 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; + } + } - u->quit_requested = 1; - request_render(u); + return pa_sink_process_msg(o, code, data, offset, memchunk); } static int jack_process(jack_nframes_t nframes, void *arg) { struct userdata *u = arg; - assert(u); - - if (jack_transport_query(u->client, NULL) == JackTransportRolling) { - unsigned c; - - pthread_mutex_lock(&u->mutex); - - u->frames_requested = nframes; - - for (c = 0; c < u->channels; c++) { - u->buffer[c] = jack_port_get_buffer(u->port[c], nframes); - assert(u->buffer[c]); - } - - request_render(u); - - pthread_cond_wait(&u->cond, &u->mutex); - - u->frames_in_buffer = nframes; - u->timestamp = jack_get_current_transport_frame(u->client); - - pthread_mutex_unlock(&u->mutex); - } - + 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 pa_usec_t sink_get_latency_cb(pa_sink *s) { - struct userdata *u; - jack_nframes_t n, l, d; - - assert(s); - u = s->userdata; - - if (jack_transport_query(u->client, NULL) != JackTransportRolling) - return 0; - - n = jack_get_current_transport_frame(u->client); - - if (n < u->timestamp) - return 0; - - d = n - u->timestamp; - l = jack_port_get_total_latency(u->client, u->port[0]) + u->frames_in_buffer; - - if (d >= l) - return 0; - - return pa_bytes_to_usec((l - d) * pa_frame_size(&s->sample_spec), &s->sample_spec); +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) { - pa_log_warn("JACK error >%s<", t); + char *s; + + s = pa_xstrndup(t, strcspn(t, "\n\r")); + pa_log_warn("JACK error >%s<", s); + pa_xfree(s); } -int pa__init(pa_core *c, pa_module*m) { +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; @@ -245,78 +271,78 @@ int pa__init(pa_core *c, pa_module*m) { jack_status_t status; const char *server_name, *client_name; uint32_t channels = 0; - int do_connect = 1; + pa_bool_t do_connect = TRUE; unsigned i; const char **ports = NULL, **p; - char *t; - - assert(c); - assert(m); + pa_sink_new_data data; + + 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."); + 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."); + 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"); + client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Sink"); u = pa_xnew0(struct userdata, 1); - m->userdata = u; - u->core = c; + u->core = m->core; u->module = m; - u->pipe_fds[0] = u->pipe_fds[1] = -1; - u->pipe_fd_type = 0; - - pthread_mutex_init(&u->mutex, NULL); - pthread_cond_init(&u->cond, NULL); - - if (pipe(u->pipe_fds) < 0) { - pa_log("pipe() failed: %s", pa_cstrerror(errno)); - goto fail; - } + m->userdata = u; + u->saved_frame_time_valid = FALSE; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + /* 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_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); - pa_make_nonblock_fd(u->pipe_fds[1]); - 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 = c->default_sample_spec.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."); + pa_log("Failed to parse channels= argument."); goto fail; } - pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); - if (pa_modargs_get_channel_map(ma, &map) < 0 || map.channels != channels) { - pa_log("failed to parse channel_map= argument."); + pa_channel_map_init_extend(&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; - assert(pa_sample_spec_valid(&ss)); + 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))) { @@ -325,19 +351,40 @@ int pa__init(pa_core *c, pa_module*m) { } } - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink."); + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack"); + if (server_name) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack sink (%s)", jack_get_client_name(u->client)); + pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); goto fail; } + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Jack sink (%s)", jack_get_client_name(u->client))); - pa_xfree(t); - u->sink->get_latency = sink_get_latency_cb; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); 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"); @@ -348,25 +395,24 @@ int pa__init(pa_core *c, pa_module*m) { for (i = 0, p = ports; i < ss.channels; i++, p++) { if (!*p) { - pa_log("not enough physical output ports, leaving unconnected."); + pa_log("Not enough physical output ports, leaving unconnected."); break; } - pa_log_info("connecting %s to %s", jack_port_name(u->port[i]), *p); - + 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); + pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); break; } } - } - u->io_event = c->mainloop->io_new(c->mainloop, u->pipe_fds[0], PA_IO_EVENT_INPUT, io_event_cb, u); - + pa_sink_put(u->sink); + free(ports); pa_modargs_free(ma); - + return 0; fail: @@ -374,15 +420,16 @@ fail: pa_modargs_free(ma); free(ports); - - pa__done(c, m); + + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; @@ -390,20 +437,27 @@ void pa__done(pa_core *c, pa_module*m) { if (u->client) jack_client_close(u->client); - if (u->io_event) - c->mainloop->io_free(u->io_event); + if (u->sink) + pa_sink_unlink(u->sink); - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); } - if (u->pipe_fds[0] >= 0) - close(u->pipe_fds[0]); - if (u->pipe_fds[1] >= 0) - close(u->pipe_fds[1]); + 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); - pthread_mutex_destroy(&u->mutex); - pthread_cond_destroy(&u->cond); pa_xfree(u); } diff --git a/src/modules/module-jack-source.c b/src/modules/module-jack-source.c index 5270b241..03f9d15c 100644 --- a/src/modules/module-jack-source.c +++ b/src/modules/module-jack-source.c @@ -1,18 +1,18 @@ -/* $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 @@ -26,46 +26,49 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> -#include <pthread.h> #include <jack/jack.h> #include <pulse/xmalloc.h> #include <pulsecore/core-error.h> -#include <pulsecore/iochannel.h> #include <pulsecore/source.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> -#include <pulse/mainloop-api.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" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Jack Source") -PA_MODULE_VERSION(PACKAGE_VERSION) +/* 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>") + "channel_map=<channel map>"); #define DEFAULT_SOURCE_NAME "jack_in" struct userdata { pa_core *core; pa_module *module; - pa_source *source; unsigned channels; @@ -73,19 +76,15 @@ struct userdata { jack_port_t* port[PA_CHANNELS_MAX]; jack_client_t *client; - pthread_mutex_t mutex; - pthread_cond_t cond; - - void * buffer[PA_CHANNELS_MAX]; - jack_nframes_t frames_posted; - int quit_requested; + pa_thread_mq thread_mq; + pa_asyncmsgq *jack_msgq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; - int pipe_fds[2]; - int pipe_fd_type; - pa_io_event *io_event; + pa_thread *thread; - jack_nframes_t frames_in_buffer; - jack_nframes_t timestamp; + jack_nframes_t saved_frame_time; + pa_bool_t saved_frame_time_valid; }; static const char* const valid_modargs[] = { @@ -98,146 +97,150 @@ static const char* const valid_modargs[] = { NULL }; -static void stop_source(struct userdata *u) { - assert (u); - - jack_client_close(u->client); - u->client = NULL; - u->core->mainloop->io_free(u->io_event); - u->io_event = NULL; - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - pa_module_unload_request(u->module); -} +enum { + SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX, + SOURCE_MESSAGE_ON_SHUTDOWN +}; -static void io_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { - struct userdata *u = userdata; - char x; - - assert(m); - assert(flags == PA_IO_EVENT_INPUT); - assert(u); - assert(u->pipe_fds[0] == fd); - - pa_read(fd, &x, 1, &u->pipe_fd_type); - - if (u->quit_requested) { - stop_source(u); - u->quit_requested = 0; - return; - } - - pthread_mutex_lock(&u->mutex); - - if (u->frames_posted > 0) { - unsigned fs; - jack_nframes_t frame_idx; - pa_memchunk chunk; - void *p; - - fs = pa_frame_size(&u->source->sample_spec); - - chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length = u->frames_posted * fs); - chunk.index = 0; - - p = pa_memblock_acquire(chunk.memblock); - - for (frame_idx = 0; frame_idx < u->frames_posted; frame_idx ++) { - unsigned c; - - for (c = 0; c < u->channels; c++) { - float *s = ((float*) u->buffer[c]) + frame_idx; - float *d = ((float*) ((uint8_t*) p + chunk.index)) + (frame_idx * u->channels) + c; - - *d = *s; - } - } +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; - pa_memblock_release(chunk.memblock); - - pa_source_post(u->source, &chunk); - pa_memblock_unref(chunk.memblock); + switch (code) { - u->frames_posted = 0; - - pthread_cond_signal(&u->cond); - } + case SOURCE_MESSAGE_POST: - pthread_mutex_unlock(&u->mutex); -} + /* Handle the new block from the JACK thread */ + pa_assert(chunk); + pa_assert(chunk->length > 0); -static void request_post(struct userdata *u) { - char c = 'x'; - assert(u); + if (u->source->thread_info.state == PA_SOURCE_RUNNING) + pa_source_post(u->source, chunk); - assert(u->pipe_fds[1] >= 0); - pa_write(u->pipe_fds[1], &c, 1, &u->pipe_fd_type); -} + u->saved_frame_time = offset; + u->saved_frame_time_valid = TRUE; -static void jack_shutdown(void *arg) { - struct userdata *u = arg; - assert(u); + 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 */ - u->quit_requested = 1; - request_post(u); + 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; - assert(u); - - if (jack_transport_query(u->client, NULL) == JackTransportRolling) { - unsigned c; - - pthread_mutex_lock(&u->mutex); - - u->frames_posted = nframes; - - for (c = 0; c < u->channels; c++) { - u->buffer[c] = jack_port_get_buffer(u->port[c], nframes); - assert(u->buffer[c]); - } - - request_post(u); - - pthread_cond_wait(&u->cond, &u->mutex); - - u->frames_in_buffer = nframes; - u->timestamp = jack_get_current_transport_frame(u->client); - - pthread_mutex_unlock(&u->mutex); - } - + 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 pa_usec_t source_get_latency_cb(pa_source *s) { - struct userdata *u; - jack_nframes_t n, l, d; - - assert(s); - u = s->userdata; - - if (jack_transport_query(u->client, NULL) != JackTransportRolling) - return 0; - - n = jack_get_current_transport_frame(u->client); - - if (n < u->timestamp) - return 0; - - d = n - u->timestamp; - l = jack_port_get_total_latency(u->client, u->port[0]); - - return pa_bytes_to_usec((l + d) * pa_frame_size(&s->sample_spec), &s->sample_spec); +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) { - pa_log_warn("JACK error >%s<", 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); } -int pa__init(pa_core *c, pa_module*m) { +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; @@ -245,78 +248,72 @@ int pa__init(pa_core *c, pa_module*m) { jack_status_t status; const char *server_name, *client_name; uint32_t channels = 0; - int do_connect = 1; + pa_bool_t do_connect = TRUE; unsigned i; const char **ports = NULL, **p; - char *t; - - assert(c); - assert(m); + pa_source_new_data data; + + 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."); + 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."); + 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"); + client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Source"); u = pa_xnew0(struct userdata, 1); - m->userdata = u; - u->core = c; + u->core = m->core; u->module = m; - u->pipe_fds[0] = u->pipe_fds[1] = -1; - u->pipe_fd_type = 0; - - pthread_mutex_init(&u->mutex, NULL); - pthread_cond_init(&u->cond, NULL); - - if (pipe(u->pipe_fds) < 0) { - pa_log("pipe() failed: %s", pa_cstrerror(errno)); - goto fail; - } + m->userdata = u; + u->saved_frame_time_valid = FALSE; + + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + u->jack_msgq = pa_asyncmsgq_new(0); + u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); - pa_make_nonblock_fd(u->pipe_fds[1]); - 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 = c->default_sample_spec.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_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); - if (pa_modargs_get_channel_map(ma, &map) < 0 || map.channels != channels) { + pa_channel_map_init_extend(&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; - assert(pa_sample_spec_valid(&ss)); + 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))) { @@ -325,19 +322,40 @@ int pa__init(pa_core *c, pa_module*m) { } } - if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { - pa_log("failed to create source."); + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack"); + if (server_name) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack source (%s)", jack_get_client_name(u->client)); + pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log("Failed to create source."); goto fail; } + u->source->parent.process_msg = source_process_msg; u->source->userdata = u; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("Jack source (%s)", jack_get_client_name(u->client))); - pa_xfree(t); - u->source->get_latency = source_get_latency_cb; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); 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"); @@ -353,7 +371,7 @@ int pa__init(pa_core *c, pa_module*m) { } 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; @@ -362,11 +380,11 @@ int pa__init(pa_core *c, pa_module*m) { } - u->io_event = c->mainloop->io_new(c->mainloop, u->pipe_fds[0], PA_IO_EVENT_INPUT, io_event_cb, u); - + pa_source_put(u->source); + free(ports); pa_modargs_free(ma); - + return 0; fail: @@ -374,15 +392,15 @@ fail: pa_modargs_free(ma); free(ports); - - pa__done(c, m); + + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + pa_assert(m); if (!(u = m->userdata)) return; @@ -390,20 +408,27 @@ void pa__done(pa_core *c, pa_module*m) { if (u->client) jack_client_close(u->client); - if (u->io_event) - c->mainloop->io_free(u->io_event); + if (u->source) + pa_source_unlink(u->source); - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); } - if (u->pipe_fds[0] >= 0) - close(u->pipe_fds[0]); - if (u->pipe_fds[1] >= 0) - close(u->pipe_fds[1]); + 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); - pthread_mutex_destroy(&u->mutex); - pthread_cond_destroy(&u->cond); 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..3e0babfa --- /dev/null +++ b/src/modules/module-ladspa-sink.c @@ -0,0 +1,799 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General 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 <pulsecore/ltdl-helper.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>"); + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +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_memblockq *memblockq; +}; + +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; + + /* Get the latency of the master sink */ + if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + /* Add the latency internal to our sink input on top */ + usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + + *((pa_usec_t*) data) = usec; + 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_IS_LINKED(state) && + u->sink_input && + PA_SINK_INPUT_IS_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 void sink_request_rewind(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from I/O thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + float *src, *dst; + size_t fs; + unsigned n, c; + pa_memchunk tchunk; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return -1; + + while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { + pa_memchunk nchunk; + + pa_sink_render(u->sink, nbytes, &nchunk); + pa_memblockq_push(u->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + + tchunk.length = PA_MIN(nbytes, tchunk.length); + pa_assert(tchunk.length > 0); + + fs = pa_frame_size(&i->sample_spec); + n = PA_MIN(tchunk.length, u->block_size) / fs; + + pa_assert(n > 0); + + chunk->index = 0; + chunk->length = n*fs; + chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); + + pa_memblockq_drop(u->memblockq, chunk->length); + + src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); + dst = (float*) pa_memblock_acquire(chunk->memblock); + + for (c = 0; c < u->channels; c++) { + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input, sizeof(float), src+c, u->channels*sizeof(float), n); + u->descriptor->run(u->handle[c], n); + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst+c, u->channels*sizeof(float), u->output, sizeof(float), n); + } + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(chunk->memblock); + + pa_memblock_unref(tchunk.memblock); + + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(nbytes > 0); + + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return; + + if (u->sink->thread_info.rewind_nbytes > 0) { + size_t max_rewrite, amount; + + max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq); + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); + u->sink->thread_info.rewind_nbytes = 0; + + if (amount > 0) { + unsigned c; + + pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE); + pa_sink_process_rewind(u->sink, amount); + + pa_log_debug("Resetting plugin"); + + /* Reset the plugin */ + if (u->descriptor->deactivate) + for (c = 0; c < u->channels; c++) + u->descriptor->deactivate(u->handle[c]); + if (u->descriptor->activate) + for (c = 0; c < u->channels; c++) + u->descriptor->activate(u->handle[c]); + } + } + + pa_memblockq_rewind(u->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); + pa_sink_set_max_rewind(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_set_max_request(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_update_latency_range(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +} + +/* 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); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_detach_within_thread(u->sink); + pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); +} + +/* 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); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + 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); + + pa_sink_update_latency_range(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); +} + +/* 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_unlink(u->sink); + pa_sink_input_unlink(u->sink_input); + + pa_sink_unref(u->sink); + u->sink = NULL; + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_module_unload_request(u->module); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); + } +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + char *t; + const char *z; + pa_sink *master; + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_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; + + 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; + u->sink = NULL; + u->sink_input = NULL; + u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL); + + 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) pa_load_sym(m->dl, NULL, "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'.", label, plugin); + 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) { + double f; + + if (*k == 0) { + use_default[p++] = TRUE; + pa_xfree(k); + continue; + } + + if (pa_atod(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); + 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]); + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name); + sink_data.namereg_fail = FALSE; + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &map); + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin); + pa_proplist_sets(sink_data.proplist, "device.ladspa.label", d->Label); + pa_proplist_sets(sink_data.proplist, "device.ladspa.name", d->Name); + pa_proplist_sets(sink_data.proplist, "device.ladspa.maker", d->Maker); + pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright); + pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID); + + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { + 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->update_requested_latency = sink_update_requested_latency; + u->sink->request_rewind = sink_request_rewind; + u->sink->userdata = u; + + 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(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + sink_input_data.sink = u->master; + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream"); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + + u->sink_input = pa_sink_input_new(m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_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->state_change = sink_input_state_change_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); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa_xfree(use_default); + + 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) { + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + } + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + 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); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + 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 index 18b2ddf1..0570a6a1 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -1,18 +1,18 @@ -/* $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 @@ -24,12 +24,12 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> -#include <lirc/lirc_client.h> #include <stdlib.h> +#include <lirc/lirc_client.h> + #include <pulse/xmalloc.h> #include <pulsecore/module.h> @@ -37,13 +37,15 @@ #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_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>") +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", @@ -66,27 +68,28 @@ 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; - assert(io); - assert(u); + + pa_assert(io); + pa_assert(u); if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { - pa_log("lost connection to LIRC daemon."); + 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_log_debug("Raw IR code '%s'", c); pa_xfree(c); - + while (lirc_code2char(u->config, code, &name) == 0 && name) { enum { INVALID, @@ -96,9 +99,9 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC RESET, MUTE_TOGGLE } volchange = INVALID; - - pa_log_info("translated IR code '%s'", name); - + + pa_log_info("Translated IR code '%s'", name); + if (strcasecmp(name, "volume-up") == 0) volchange = UP; else if (strcasecmp(name, "volume-down") == 0) @@ -109,17 +112,17 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC volchange = MUTE_TOGGLE; else if (strcasecmp(name, "reset") == 0) volchange = RESET; - + if (volchange == INVALID) - pa_log_warn("recieved unknown IR code '%s'", name); + 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); + pa_log("Failed to get sink '%s'", u->sink_name); else { int i; - pa_cvolume cv = *pa_sink_get_volume(s, PA_MIXER_HARDWARE); + pa_cvolume cv = *pa_sink_get_volume(s); #define DELTA (PA_VOLUME_NORM/20) @@ -132,9 +135,9 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC cv.values[i] = PA_VOLUME_NORM; } - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + pa_sink_set_volume(s, &cv); break; - + case DOWN: for (i = 0; i < cv.channels; i++) { if (cv.values[i] >= DELTA) @@ -142,21 +145,21 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC else cv.values[i] = PA_VOLUME_MUTED; } - - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + + pa_sink_set_volume(s, &cv); break; - + case MUTE: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, 0); + pa_sink_set_mute(s, 0); break; - + case RESET: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, 1); + pa_sink_set_mute(s, 1); break; - + case MUTE_TOGGLE: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, !pa_sink_get_mute(s, PA_MIXER_HARDWARE)); + pa_sink_set_mute(s, !pa_sink_get_mute(s)); break; case INVALID: @@ -170,32 +173,33 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC pa_xfree(code); return; - + fail: u->module->core->mainloop->io_free(u->io); u->io = NULL; pa_module_unload_request(u->module); - free(code); + pa_xfree(code); } - -int pa__init(pa_core *c, pa_module*m) { + +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - assert(c && m); + + 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_xmalloc(sizeof(struct userdata)); + m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->io = NULL; u->config = NULL; @@ -212,13 +216,13 @@ int pa__init(pa_core *c, pa_module*m) { pa_log("lirc_readconfig() failed."); goto fail; } - - u->io = c->mainloop->io_new(c->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); + + 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: @@ -226,14 +230,13 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; diff --git a/src/modules/module-match.c b/src/modules/module-match.c index eb5de64e..769a6b59 100644 --- a/src/modules/module-match.c +++ b/src/modules/module-match.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <sys/types.h> @@ -45,10 +44,11 @@ #include "module-match-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Playback stream expression matching module") -PA_MODULE_USAGE("table=<filename>") -PA_MODULE_VERSION(PACKAGE_VERSION) +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" @@ -78,17 +78,21 @@ static int load_rules(struct userdata *u, const char *filename) { struct rule *end = NULL; char *fn = NULL; - f = filename ? - fopen(fn = pa_xstrdup(filename), "r") : - pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn, "r"); + pa_assert(u); + + if (filename) + f = fopen(fn = pa_xstrdup(filename), "r"); + else + f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn); if (!f) { - pa_log("failed to open file '%s': %s", fn, pa_cstrerror(errno)); + pa_xfree(fn); + pa_log("Failed to open file config file: %s", pa_cstrerror(errno)); goto finish; } pa_lock_fd(fileno(f), 1); - + while (!feof(f)) { char *d, *v; pa_volume_t volume; @@ -96,12 +100,12 @@ static int load_rules(struct userdata *u, const char *filename) { 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 ) @@ -110,7 +114,7 @@ static int load_rules(struct userdata *u, const char *filename) { 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; @@ -124,13 +128,13 @@ static int load_rules(struct userdata *u, const char *filename) { volume = (pa_volume_t) k; - + if (regcomp(®ex, ln, REG_EXTENDED|REG_NOSUB) != 0) { pa_log("[%s:%u] invalid regular expression", filename, n); goto finish; } - rule = pa_xmalloc(sizeof(struct rule)); + rule = pa_xnew(struct rule, 1); rule->regex = regex; rule->volume = volume; rule->next = NULL; @@ -140,12 +144,12 @@ static int load_rules(struct userdata *u, const char *filename) { else u->rules = rule; end = rule; - + *d = 0; } ret = 0; - + finish: if (f) { pa_lock_fd(fileno(f), 0); @@ -162,7 +166,10 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v struct userdata *u = userdata; pa_sink_input *si; struct rule *r; - assert(c && u); + const char *n; + + pa_assert(c); + pa_assert(u); if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW)) return; @@ -170,61 +177,63 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) return; - if (!si->name) + if (!(n = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME))) return; - + for (r = u->rules; r; r = r->next) { - if (!regexec(&r->regex, si->name, 0, NULL, 0)) { + if (!regexec(&r->regex, n, 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, r->volume, si->sample_spec.channels); + pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume); + pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); pa_sink_input_set_volume(si, &cv); } } } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); goto fail; } - u = pa_xmalloc(sizeof(struct userdata)); + 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(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u); + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u); pa_modargs_free(ma); return 0; fail: - pa__done(c, m); + pa__done(m); if (ma) pa_modargs_free(ma); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata* u; struct rule *r, *n; - assert(c && m); + + 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; @@ -234,5 +243,3 @@ void pa__done(pa_core *c, pa_module*m) { pa_xfree(u); } - - diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index 37234d92..4388e49c 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -1,18 +1,18 @@ -/* $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 @@ -24,7 +24,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <string.h> #include <stdlib.h> @@ -45,10 +44,11 @@ #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_USAGE("device=<evdev device> sink=<sink name>") +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" @@ -78,26 +78,27 @@ struct userdata { 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; - assert(io); - assert(u); + + pa_assert(io); + pa_assert(u); if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { - pa_log("lost connection to evdev device."); + 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)); + 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); + pa_log_debug("Key code=%u, value=%u", ev.code, ev.value); switch (ev.code) { case KEY_VOLUMEDOWN: volchange = DOWN; break; @@ -107,15 +108,15 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC 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); + pa_log("Failed to get sink '%s'", u->sink_name); else { int i; - pa_cvolume cv = *pa_sink_get_volume(s, PA_MIXER_HARDWARE); - + 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++) { @@ -125,9 +126,9 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC cv.values[i] = PA_VOLUME_NORM; } - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + pa_sink_set_volume(s, &cv); break; - + case DOWN: for (i = 0; i < cv.channels; i++) { if (cv.values[i] >= DELTA) @@ -135,13 +136,13 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC else cv.values[i] = PA_VOLUME_MUTED; } - - pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv); + + pa_sink_set_volume(s, &cv); break; - + case MUTE_TOGGLE: - pa_sink_set_mute(s, PA_MIXER_HARDWARE, !pa_sink_get_mute(s, PA_MIXER_HARDWARE)); + pa_sink_set_mute(s, !pa_sink_get_mute(s)); break; case INVALID: @@ -153,7 +154,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC } return; - + fail: u->module->core->mainloop->io_free(u->io); u->io = NULL; @@ -162,22 +163,24 @@ fail: } #define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) - -int pa__init(pa_core *c, pa_module*m) { + +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]; - assert(c && m); + + 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)); + 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)); @@ -219,14 +222,14 @@ int pa__init(pa_core *c, pa_module*m) { } if (!test_bit(EV_KEY, evtype_bitmask)) { - pa_log("device has no keys."); + pa_log("Device has no keys."); goto fail; } - u->io = c->mainloop->io_new(c->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); + 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: @@ -234,14 +237,14 @@ fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + + pa_assert(m); if (!(u = m->userdata)) return; @@ -250,7 +253,7 @@ void pa__done(pa_core *c, pa_module*m) { m->core->mainloop->io_free(u->io); if (u->fd >= 0) - close(u->fd); + 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 index dd3b4abe..1a6f5368 100644 --- a/src/modules/module-native-protocol-fd.c +++ b/src/modules/module-native-protocol-fd.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,10 +24,10 @@ #endif #include <stdio.h> -#include <assert.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> @@ -35,9 +35,10 @@ #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_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", @@ -46,25 +47,26 @@ static const char* const valid_modargs[] = { NULL, }; -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; int fd, r = -1; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + pa_log("Failed to parse module arguments."); goto finish; } if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) { - pa_log("invalid file descriptor."); + pa_log("Invalid file descriptor."); goto finish; } - - io = pa_iochannel_new(c->mainloop, fd, fd); - if (!(m->userdata = pa_protocol_native_new_iochannel(c, io, m, ma))) { + 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; } @@ -74,12 +76,12 @@ int pa__init(pa_core *c, pa_module*m) { finish: if (ma) pa_modargs_free(ma); - + return r; } -void pa__done(pa_core *c, pa_module*m) { - assert(c && m); +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 index 50e58853..604ab158 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -26,7 +26,6 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> @@ -36,37 +35,48 @@ #include <pulse/timeval.h> #include <pulse/xmalloc.h> -#include <pulsecore/iochannel.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_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>") + "sink_name=<name of sink> " + "channel_map=<channel map> " + "description=<description for the sink>"); #define DEFAULT_SINK_NAME "null" +#define MAX_LATENCY_USEC (PA_USEC_PER_SEC * 2) struct userdata { pa_core *core; pa_module *module; pa_sink *sink; - pa_time_event *time_event; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + size_t block_size; - uint64_t n_bytes; - struct timeval start_time; + pa_usec_t block_usec; + pa_usec_t timestamp; }; static const char* const valid_modargs[] = { @@ -79,103 +89,249 @@ static const char* const valid_modargs[] = { NULL }; -static void time_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) { - struct userdata *u = userdata; - pa_memchunk chunk; - struct timeval ntv = *tv; - size_t l; +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) + u->timestamp = pa_rtclock_usec(); + + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t now; + + now = pa_rtclock_usec(); + *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0; + + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + u = s->userdata; + pa_assert(u); + + u->block_usec = pa_sink_get_requested_latency_within_thread(s); +} + +static void process_rewind(struct userdata *u, pa_usec_t now) { + size_t rewind_nbytes, in_buffer; + pa_usec_t delay; + + pa_assert(u); + + /* Figure out how much we shall rewind and reset the counter */ + rewind_nbytes = u->sink->thread_info.rewind_nbytes; + u->sink->thread_info.rewind_nbytes = 0; + + pa_assert(rewind_nbytes > 0); + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + if (u->timestamp <= now) + return; + + delay = u->timestamp - now; + in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec); + + if (in_buffer <= 0) + return; + + if (rewind_nbytes > in_buffer) + rewind_nbytes = in_buffer; + + pa_sink_process_rewind(u->sink, rewind_nbytes); + u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec); + + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); +} + +static void process_render(struct userdata *u, pa_usec_t now) { + size_t ate = 0; + + pa_assert(u); + + /* This is the configured latency. Sink inputs connected to us + might not have a single frame more than the maxrequest value + queed. Hence: at maximum read this many bytes from the sink + inputs. */ - assert(u); + /* Fill the buffer up the the latency size */ + while (u->timestamp < now + u->block_usec) { + pa_memchunk chunk; - if (pa_sink_render(u->sink, u->block_size, &chunk) >= 0) { - l = chunk.length; + pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); pa_memblock_unref(chunk.memblock); - } else - l = u->block_size; - pa_timeval_add(&ntv, pa_bytes_to_usec(l, &u->sink->sample_spec)); - m->time_restart(e, &ntv); +/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */ + u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); - u->n_bytes += l; + ate += chunk.length; + + if (ate >= u->sink->thread_info.max_request) + break; + } + +/* pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, (unsigned long) nbytes); */ } -static pa_usec_t get_latency(pa_sink *s) { - struct userdata *u = s->userdata; - pa_usec_t a, b; - struct timeval now; +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); + + u->timestamp = pa_rtclock_usec(); + + for (;;) { + int ret; + + /* Render some data and drop it immediately */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + pa_usec_t now; - a = pa_timeval_diff(pa_gettimeofday(&now), &u->start_time); - b = pa_bytes_to_usec(u->n_bytes, &s->sample_spec); + now = pa_rtclock_usec(); - return b > a ? b - a : 0; + if (u->sink->thread_info.rewind_nbytes > 0) + process_rewind(u, now); + + if (u->timestamp <= now) + process_render(u, now); + + 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, 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"); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u = NULL; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; - - assert(c); - assert(m); - + pa_sink_new_data data; + + pa_assert(m); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + pa_log("Failed to parse module arguments."); goto fail; } - ss = c->default_sample_spec; + 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."); + pa_log("Invalid sample format specification or channel map"); goto fail; } - - u = pa_xnew0(struct userdata, 1); - u->core = c; + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; u->module = m; - m->userdata = u; - - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink."); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink object."); goto fail; } - u->sink->get_latency = get_latency; + u->sink->parent.process_msg = sink_process_msg; + u->sink->update_requested_latency = sink_update_requested_latency_cb; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, pa_modargs_get_value(ma, "description", "NULL sink")); - u->n_bytes = 0; - pa_gettimeofday(&u->start_time); - - u->time_event = c->mainloop->time_new(c->mainloop, &u->start_time, time_callback, u); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + pa_sink_set_latency_range(u->sink, (pa_usec_t) -1, MAX_LATENCY_USEC); + u->block_usec = u->sink->thread_info.max_latency; + + u->sink->thread_info.max_rewind = + u->sink->thread_info.max_request = + pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + pa_sink_put(u->sink); - u->block_size = pa_bytes_per_second(&ss) / 10; - pa_modargs_free(ma); - + return 0; fail: if (ma) pa_modargs_free(ma); - - pa__done(c, m); + + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->core->mainloop->time_free(u->time_event); + 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-mmap.c b/src/modules/module-oss-mmap.c deleted file mode 100644 index 39a8511f..00000000 --- a/src/modules/module-oss-mmap.c +++ /dev/null @@ -1,634 +0,0 @@ -/* $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 <sys/soundcard.h> -#include <sys/ioctl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <stdio.h> -#include <assert.h> -#include <errno.h> -#include <string.h> -#include <fcntl.h> -#include <unistd.h> -#include <limits.h> - -#ifdef HAVE_SYS_MMAN_H -#include <sys/mman.h> -#endif - -#include <pulse/xmalloc.h> -#include <pulse/util.h> - -#include <pulsecore/core-error.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 "oss-util.h" -#include "module-oss-mmap-symdef.h" - -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("OSS Sink/Source (mmap)") -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> " - "fragments=<number of fragments> " - "fragment_size=<fragment size> " - "channel_map=<channel map>") - -struct userdata { - pa_sink *sink; - pa_source *source; - pa_core *core; - pa_sample_spec sample_spec; - - size_t in_fragment_size, out_fragment_size; - unsigned in_fragments, out_fragments; - unsigned out_blocks_saved, in_blocks_saved; - - int fd; - - void *in_mmap, *out_mmap; - size_t in_mmap_length, out_mmap_length; - - pa_io_event *io_event; - - pa_memblock **in_memblocks, **out_memblocks; - unsigned out_current, in_current; - pa_module *module; -}; - -static const char* const valid_modargs[] = { - "sink_name", - "source_name", - "device", - "record", - "playback", - "fragments", - "fragment_size", - "format", - "rate", - "channels", - "channel_map", - NULL -}; - -#define DEFAULT_DEVICE "/dev/dsp" -#define DEFAULT_NFRAGS 12 -#define DEFAULT_FRAGSIZE 1024 - -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 clear_up(struct userdata *u) { - assert(u); - - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - } - - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - } - - if (u->in_mmap && u->in_mmap != MAP_FAILED) { - munmap(u->in_mmap, u->in_mmap_length); - u->in_mmap = NULL; - } - - if (u->out_mmap && u->out_mmap != MAP_FAILED) { - munmap(u->out_mmap, u->out_mmap_length); - u->out_mmap = NULL; - } - - if (u->io_event) { - u->core->mainloop->io_free(u->io_event); - u->io_event = NULL; - } - - if (u->fd >= 0) { - close(u->fd); - u->fd = -1; - } -} - -static void out_fill_memblocks(struct userdata *u, unsigned n) { - assert(u && u->out_memblocks); - - while (n > 0) { - pa_memchunk chunk; - - if (u->out_memblocks[u->out_current]) - pa_memblock_unref_fixed(u->out_memblocks[u->out_current]); - - chunk.memblock = u->out_memblocks[u->out_current] = - pa_memblock_new_fixed( - u->core->mempool, - (uint8_t*) u->out_mmap+u->out_fragment_size*u->out_current, - u->out_fragment_size, - 1); - assert(chunk.memblock); - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; - - pa_sink_render_into_full(u->sink, &chunk); - - u->out_current++; - while (u->out_current >= u->out_fragments) - u->out_current -= u->out_fragments; - - n--; - } -} - -static void do_write(struct userdata *u) { - struct count_info info; - assert(u && u->sink); - - update_usage(u); - - if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); - - clear_up(u); - pa_module_unload_request(u->module); - return; - } - - info.blocks += u->out_blocks_saved; - u->out_blocks_saved = 0; - - if (!info.blocks) - return; - - out_fill_memblocks(u, info.blocks); -} - -static void in_post_memblocks(struct userdata *u, unsigned n) { - assert(u && u->in_memblocks); - - while (n > 0) { - pa_memchunk chunk; - - if (!u->in_memblocks[u->in_current]) { - chunk.memblock = u->in_memblocks[u->in_current] = pa_memblock_new_fixed(u->core->mempool, (uint8_t*) u->in_mmap+u->in_fragment_size*u->in_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_current++; - while (u->in_current >= u->in_fragments) - u->in_current -= u->in_fragments; - - n--; - } -} - -static void in_clear_memblocks(struct userdata*u, unsigned n) { - unsigned i = u->in_current; - assert(u && u->in_memblocks); - - if (n > u->in_fragments) - n = u->in_fragments; - - while (n > 0) { - if (u->in_memblocks[i]) { - pa_memblock_unref_fixed(u->in_memblocks[i]); - u->in_memblocks[i] = NULL; - } - - i++; - while (i >= u->in_fragments) - i -= u->in_fragments; - - n--; - } -} - -static void do_read(struct userdata *u) { - struct count_info info; - assert(u && u->source); - - update_usage(u); - - if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); - - clear_up(u); - pa_module_unload_request(u->module); - return; - } - - info.blocks += u->in_blocks_saved; - u->in_blocks_saved = 0; - - if (!info.blocks) - return; - - in_post_memblocks(u, info.blocks); - in_clear_memblocks(u, u->in_fragments/2); -} - -static void io_callback(pa_mainloop_api *m, pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t f, void *userdata) { - struct userdata *u = userdata; - assert (u && u->core->mainloop == m && u->io_event == e); - - if (f & PA_IO_EVENT_ERROR) { - clear_up(u); - pa_module_unload_request(u->module); - return; - } - - if (f & PA_IO_EVENT_INPUT) - do_read(u); - if (f & PA_IO_EVENT_OUTPUT) - do_write(u); -} - -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - struct count_info info; - size_t bpos, n, total; - assert(s && u); - - if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); - return 0; - } - - u->out_blocks_saved += info.blocks; - - total = u->out_fragments * u->out_fragment_size; - bpos = ((u->out_current + u->out_blocks_saved) * u->out_fragment_size) % total; - - if (bpos <= (size_t) info.ptr) - n = total - (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, &s->sample_spec); -} - -static pa_usec_t source_get_latency_cb(pa_source *s) { - struct userdata *u = s->userdata; - struct count_info info; - size_t bpos, n, total; - assert(s && u); - - if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); - return 0; - } - - u->in_blocks_saved += info.blocks; - - total = u->in_fragments * u->in_fragment_size; - bpos = ((u->in_current + u->in_blocks_saved) * u->in_fragment_size) % total; - - if (bpos <= (size_t) info.ptr) - n = info.ptr - bpos; - else - n = (u->in_fragments * u->in_fragment_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, &s->sample_spec); -} - -static int sink_get_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; - - if (pa_oss_get_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; - } - - return 0; -} - -static int sink_set_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; - - if (pa_oss_set_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; - return -1; - } - - return 0; -} - -static int source_get_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; - - if (pa_oss_get_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; - } - - return 0; -} - -static int source_set_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; - - if (pa_oss_set_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; - return -1; - } - - return 0; -} - -int pa__init(pa_core *c, pa_module*m) { - struct audio_buf_info info; - struct userdata *u = NULL; - const char *p; - int nfrags, frag_size; - int mode, caps; - int enable_bits = 0, zero = 0; - int playback = 1, record = 1; - pa_modargs *ma = NULL; - char hwdesc[64], *t; - pa_channel_map map; - const char *name; - char *name_buf = NULL; - int namereg_fail; - - assert(c); - assert(m); - - m->userdata = u = pa_xnew0(struct userdata, 1); - u->module = m; - u->fd = -1; - u->core = c; - - 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 arguments."); - 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)); - - nfrags = DEFAULT_NFRAGS; - frag_size = DEFAULT_FRAGSIZE; - 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; - } - - u->sample_spec = c->default_sample_spec; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &u->sample_spec, &map, PA_CHANNEL_MAP_OSS) < 0) { - pa_log("failed to parse sample specification or channel map"); - goto fail; - } - - if ((u->fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0) - goto fail; - - if (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_TRIGGER)) { - pa_log("OSS device not mmap capable."); - goto fail; - } - - pa_log_info("device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); - - if (pa_oss_get_hw_description(p, hwdesc, sizeof(hwdesc)) >= 0) - pa_log_info("hardware name is '%s'.", hwdesc); - else - hwdesc[0] = 0; - - if (nfrags >= 2 && frag_size >= 1) - if (pa_oss_set_fragments(u->fd, nfrags, frag_size) < 0) - goto fail; - - if (pa_oss_auto_format(u->fd, &u->sample_spec) < 0) - goto fail; - - if (mode != O_WRONLY) { - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { - pa_log("SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); - goto fail; - } - - pa_log_info("input -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->in_mmap_length = (u->in_fragment_size = info.fragsize) * (u->in_fragments = info.fragstotal); - - if ((u->in_mmap = mmap(NULL, u->in_mmap_length, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { - if (mode == O_RDWR) { - pa_log("mmap failed for input. Changing to O_WRONLY mode."); - mode = O_WRONLY; - } else { - pa_log("mmap(): %s", pa_cstrerror(errno)); - goto fail; - } - } else { - 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(p)); - namereg_fail = 0; - } - - if (!(u->source = pa_source_new(c, __FILE__, name, namereg_fail, &u->sample_spec, &map))) - goto fail; - - u->source->userdata = u; - u->source->get_latency = source_get_latency_cb; - u->source->get_hw_volume = source_get_hw_volume; - u->source->set_hw_volume = source_set_hw_volume; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("OSS PCM/mmap() on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); - pa_xfree(t); - u->source->is_hardware = 1; - - u->in_memblocks = pa_xnew0(pa_memblock*, u->in_fragments); - - enable_bits |= PCM_ENABLE_INPUT; - } - } - - pa_xfree(name_buf); - name_buf = NULL; - - if (mode != O_RDONLY) { - if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { - pa_log("SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); - goto fail; - } - - pa_log_info("output -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->out_mmap_length = (u->out_fragment_size = info.fragsize) * (u->out_fragments = info.fragstotal); - - if ((u->out_mmap = mmap(NULL, u->out_mmap_length, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { - if (mode == O_RDWR) { - pa_log("mmap filed for output. Changing to O_RDONLY mode."); - mode = O_RDONLY; - } else { - pa_log("mmap(): %s", pa_cstrerror(errno)); - goto fail; - } - } else { - pa_silence_memory(u->out_mmap, u->out_mmap_length, &u->sample_spec); - - 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(p)); - namereg_fail = 0; - } - - if (!(u->sink = pa_sink_new(c, __FILE__, name, namereg_fail, &u->sample_spec, &map))) - goto fail; - - u->sink->get_latency = sink_get_latency_cb; - u->sink->get_hw_volume = sink_get_hw_volume; - u->sink->set_hw_volume = sink_set_hw_volume; - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("OSS PCM/mmap() on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); - pa_xfree(t); - - u->sink->is_hardware = 1; - u->out_memblocks = pa_xmalloc0(sizeof(struct memblock *)*u->out_fragments); - - enable_bits |= PCM_ENABLE_OUTPUT; - } - } - - pa_xfree(name_buf); - name_buf = NULL; - - zero = 0; - if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero) < 0) { - pa_log("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); - goto fail; - } - - if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) { - pa_log("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); - goto fail; - } - - assert(u->source || u->sink); - - u->io_event = c->mainloop->io_new(c->mainloop, u->fd, (u->source ? PA_IO_EVENT_INPUT : 0) | (u->sink ? PA_IO_EVENT_OUTPUT : 0), io_callback, u); - assert(u->io_event); - - pa_modargs_free(ma); - - /* Read mixer settings */ - if (u->source) - source_get_hw_volume(u->source); - if (u->sink) - sink_get_hw_volume(u->sink); - - return 0; - -fail: - pa__done(c, m); - - if (ma) - pa_modargs_free(ma); - - pa_xfree(name_buf); - - return -1; -} - -void pa__done(pa_core *c, pa_module*m) { - struct userdata *u; - - assert(c); - assert(m); - - if (!(u = m->userdata)) - return; - - clear_up(u); - - if (u->out_memblocks) { - unsigned i; - for (i = 0; i < u->out_fragments; i++) - if (u->out_memblocks[i]) - pa_memblock_unref_fixed(u->out_memblocks[i]); - pa_xfree(u->out_memblocks); - } - - if (u->in_memblocks) { - unsigned i; - for (i = 0; i < u->in_fragments; i++) - if (u->in_memblocks[i]) - pa_memblock_unref_fixed(u->in_memblocks[i]); - pa_xfree(u->in_memblocks); - } - - pa_xfree(u); -} diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c index 73f0d57e..76b13ecc 100644 --- a/src/modules/module-oss.c +++ b/src/modules/module-oss.c @@ -1,45 +1,65 @@ -/* $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 <assert.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/iochannel.h> +#include <pulsecore/thread.h> #include <pulsecore/sink.h> #include <pulsecore/source.h> #include <pulsecore/module.h> @@ -47,13 +67,17 @@ #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_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> " @@ -65,21 +89,48 @@ PA_MODULE_USAGE( "rate=<sample rate> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map>") + "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_iochannel *io; - pa_core *core; - pa_memchunk memchunk, silence; + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + char *device_name; + + pa_memchunk memchunk; - uint32_t in_fragment_size, out_fragment_size, sample_size; - int use_getospace, use_getispace; + 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; - pa_module *module; + 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[] = { @@ -94,324 +145,1074 @@ static const char* const valid_modargs[] = { "rate", "channels", "channel_map", + "mmap", NULL }; -#define DEFAULT_DEVICE "/dev/dsp" +static void trigger(struct userdata *u, pa_bool_t quick) { + int enable_bits = 0, zero = 0; -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)); -} + pa_assert(u); -static void clear_up(struct userdata *u) { - assert(u); - - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; + if (u->fd < 0) + return; + + pa_log_debug("trigger"); + + if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) + enable_bits |= PCM_ENABLE_INPUT; + + if (u->sink && PA_SINK_IS_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_IS_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); + } + } } - - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; +} + +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; - if (u->io) { - pa_iochannel_free(u->io); - u->io = NULL; + 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 do_write(struct userdata *u) { - pa_memchunk *memchunk; - ssize_t r; - size_t l; - int loop = 0; - - assert(u); +static void mmap_post_memblocks(struct userdata *u, unsigned n) { + pa_assert(u); + pa_assert(u->in_mmap_memblocks); - if (!u->sink || !pa_iochannel_is_writable(u->io)) - return; +/* pa_log("Mmmap reading %u blocks", n); */ - update_usage(u); + while (n > 0) { + pa_memchunk chunk; - l = u->out_fragment_size; - - if (u->use_getospace) { - audio_buf_info info; - - if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) - u->use_getospace = 0; - else { - if (info.bytes/l > 0) { - l = (info.bytes/l)*l; - loop = 1; - } + 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--; } +} - do { - void *p; - memchunk = &u->memchunk; - - if (!memchunk->length) - if (pa_sink_render(u->sink, l, memchunk) < 0) - memchunk = &u->silence; - - assert(memchunk->memblock); - assert(memchunk->length); - - p = pa_memblock_acquire(memchunk->memblock); - if ((r = pa_iochannel_write(u->io, (uint8_t*) p + memchunk->index, memchunk->length)) < 0) { - pa_memblock_release(memchunk->memblock); - pa_log("write() failed: %s", pa_cstrerror(errno)); - - clear_up(u); - pa_module_unload_request(u->module); - break; - } - pa_memblock_release(memchunk->memblock); - - if (memchunk == &u->silence) - assert(r % u->sample_size == 0); - else { - u->memchunk.index += r; - u->memchunk.length -= r; - - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - } +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; } - l = l > (size_t) r ? l - r : 0; - } while (loop && l > 0); + i++; + while (i >= u->in_nfrags) + i -= u->in_nfrags; + + n--; + } } -static void do_read(struct userdata *u) { - pa_memchunk memchunk; - ssize_t r; - size_t l; - int loop = 0; - assert(u); - - if (!u->source || !pa_iochannel_is_readable(u->io) || !pa_idxset_size(u->source->outputs)) - return; +static int mmap_read(struct userdata *u) { + struct count_info info; + pa_assert(u); + pa_assert(u->source); - update_usage(u); +/* pa_log("Mmmap reading..."); */ - l = u->in_fragment_size; + if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); + return -1; + } - if (u->use_getispace) { - audio_buf_info info; - - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) - u->use_getispace = 0; - else { - if (info.bytes/l > 0) { - l = (info.bytes/l)*l; - loop = 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); } - - do { - void *p; - memchunk.memblock = pa_memblock_new(u->core->mempool, l); - - p = pa_memblock_acquire(memchunk.memblock); - - if ((r = pa_iochannel_read(u->io, p, pa_memblock_get_length(memchunk.memblock))) < 0) { - pa_memblock_release(memchunk.memblock); - pa_memblock_unref(memchunk.memblock); - if (errno != EAGAIN) { - pa_log("read() failed: %s", pa_cstrerror(errno)); - clear_up(u); - pa_module_unload_request(u->module); - } - break; - } - pa_memblock_release(memchunk.memblock); - - assert(r <= (ssize_t) pa_memblock_get_length(memchunk.memblock)); - memchunk.length = r; - memchunk.index = 0; - - pa_source_post(u->source, &memchunk); - pa_memblock_unref(memchunk.memblock); - - l = l > (size_t) r ? l - r : 0; - } while (loop && l > 0); + + return info.blocks; } -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_write(u); - do_read(u); +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 void source_notify_cb(pa_source *s) { - struct userdata *u = s->userdata; - assert(u); - do_read(u); +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 sink_get_latency_cb(pa_sink *s) { +static pa_usec_t io_sink_get_latency(struct userdata *u) { pa_usec_t r = 0; - int arg; - struct userdata *u = s->userdata; - assert(s && u && u->sink); - if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) { - pa_log_info("device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno)); - s->get_latency = NULL; - return 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); + } - r += pa_bytes_to_usec(arg, &s->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, &s->sample_spec); + r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); return r; } -static pa_usec_t source_get_latency_cb(pa_source *s) { - struct userdata *u = s->userdata; - audio_buf_info info; - assert(s && u && u->source); - if (!u->use_getispace) - return 0; - - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { - u->use_getispace = 0; - return 0; +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); } - - if (info.bytes <= 0) - return 0; - return pa_bytes_to_usec(info.bytes, &s->sample_spec); + return r; } -static int sink_get_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; +static void build_pollfd(struct userdata *u) { + struct pollfd *pollfd; - if (pa_oss_get_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; + 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 sink_set_hw_volume(pa_sink *s) { - struct userdata *u = s->userdata; +static int sink_get_volume(pa_sink *s); +static int source_get_volume(pa_source *s); + +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; - if (pa_oss_set_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; + 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) + sink_get_volume(u->sink); + if (u->source) + source_get_volume(u->source); + + pa_log_info("Resumed successfully..."); + return 0; + +fail: + pa_close(u->fd); + u->fd = -1; + return -1; } -static int source_get_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; +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_IS_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_IS_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; - if (pa_oss_get_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - s->get_hw_volume = NULL; - return -1; } - return 0; + ret = pa_sink_process_msg(o, code, data, offset, chunk); + + if (do_trigger) + trigger(u, quick); + + return ret; } -static int source_set_hw_volume(pa_source *s) { - struct userdata *u = s->userdata; +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_IS_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_IS_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; - if (pa_oss_set_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) { - pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - s->set_hw_volume = NULL; - return -1; } - return 0; + 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_IS_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_IS_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_IS_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | + ((u->sink && PA_SINK_IS_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_core *c, pa_module*m) { +int pa__init(pa_module*m) { + struct audio_buf_info info; struct userdata *u = NULL; - const char *p; + const char *dev; int fd = -1; - int nfrags, frag_size, in_frag_size, out_frag_size; - int mode; - int record = 1, playback = 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; + char hwdesc[64]; const char *name; - char *name_buf = NULL; - int namereg_fail; - - assert(c); - assert(m); + pa_bool_t namereg_fail; + pa_sink_new_data sink_new_data; + pa_source_new_data source_new_data; + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + 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."); + pa_log("record= and playback= expect boolean argument."); goto fail; } if (!playback && !record) { - pa_log("neither playback nor record enabled for device."); + pa_log("Neither playback nor record enabled for device."); goto fail; } - mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); + mode = (playback && record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); - ss = c->default_sample_spec; + 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"); + pa_log("Failed to parse sample specification or channel map"); goto fail; } - - /* Fix latency to 100ms */ - nfrags = 12; - frag_size = pa_bytes_per_second(&ss)/128; - + + 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"); + 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(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, NULL)) < 0) + if ((fd = pa_oss_open(dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0) goto fail; - if (pa_oss_get_hw_description(p, hwdesc, sizeof(hwdesc)) >= 0) - pa_log_info("hardware name is '%s'.", hwdesc); + 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")); + + 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_set_fragments(fd, nfrags, frag_size) < 0) + goto fail; if (pa_oss_auto_format(fd, &ss) < 0) goto fail; @@ -420,152 +1221,310 @@ int pa__init(pa_core *c, pa_module*m) { pa_log("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); goto fail; } - assert(frag_size); - in_frag_size = out_frag_size = frag_size; + 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 = TRUE; + u->use_getodelay = TRUE; + 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; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->rtpoll_item = NULL; + build_pollfd(u); - u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; - u->use_getospace = u->use_getispace = 0; - if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { - pa_log_info("input -- %u fragments of size %u.", info.fragstotal, info.fragsize); - in_frag_size = info.fragsize; - u->use_getispace = 1; + 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 = TRUE; } if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { - pa_log_info("output -- %u fragments of size %u.", info.fragstotal, info.fragsize); - out_frag_size = info.fragsize; - u->use_getospace = 1; + 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 = TRUE; } + 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 = FALSE; + 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; + namereg_fail = TRUE; else { - name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(p)); - namereg_fail = 0; + name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev)); + namereg_fail = FALSE; } - - if (!(u->source = pa_source_new(c, __FILE__, name, namereg_fail, &ss, &map))) + + pa_source_new_data_init(&source_new_data); + source_new_data.driver = __FILE__; + source_new_data.module = m; + pa_source_new_data_set_name(&source_new_data, name); + source_new_data.namereg_fail = namereg_fail; + pa_source_new_data_set_sample_spec(&source_new_data, &ss); + pa_source_new_data_set_channel_map(&source_new_data, &map); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_STRING, dev); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_API, "oss"); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial"); + pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->in_hwbuf_size)); + pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->in_fragment_size)); + + u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); + pa_source_new_data_done(&source_new_data); + 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; - u->source->notify = source_notify_cb; - u->source->get_latency = source_get_latency_cb; - u->source->get_hw_volume = source_get_hw_volume; - u->source->set_hw_volume = source_set_hw_volume; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("OSS PCM on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); - pa_xfree(t); - u->source->is_hardware = 1; - } else - u->source = NULL; - - pa_xfree(name_buf); - name_buf = NULL; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + 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; + namereg_fail = TRUE; else { - name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(p)); - namereg_fail = 0; + name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev)); + namereg_fail = FALSE; } - - if (!(u->sink = pa_sink_new(c, __FILE__, name, namereg_fail, &ss, &map))) + + pa_sink_new_data_init(&sink_new_data); + sink_new_data.driver = __FILE__; + sink_new_data.module = m; + pa_sink_new_data_set_name(&sink_new_data, name); + sink_new_data.namereg_fail = namereg_fail; + pa_sink_new_data_set_sample_spec(&sink_new_data, &ss); + pa_sink_new_data_set_channel_map(&sink_new_data, &map); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_STRING, dev); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_API, "oss"); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial"); + pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->out_hwbuf_size)); + pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->out_fragment_size)); + + u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&sink_new_data); + pa_xfree(name_buf); + + if (!u->sink) { + pa_log("Failed to create sink object"); goto fail; + } - u->sink->get_latency = sink_get_latency_cb; - u->sink->get_hw_volume = sink_get_hw_volume; - u->sink->set_hw_volume = sink_set_hw_volume; + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("OSS PCM on %s%s%s%s", - p, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "")); - pa_xfree(t); - u->sink->is_hardware = 1; - } else - u->sink = NULL; - - pa_xfree(name_buf); - name_buf = NULL; - - assert(u->source || u->sink); - - u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : -1); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); - u->fd = fd; - u->memchunk.memblock = NULL; - u->memchunk.length = 0; - u->sample_size = pa_frame_size(&ss); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + u->sink->refresh_volume = TRUE; - u->out_fragment_size = out_frag_size; - u->in_fragment_size = in_frag_size; - u->silence.memblock = pa_memblock_new(u->core->mempool, u->silence.length = u->out_fragment_size); - assert(u->silence.memblock); - pa_silence_memblock(u->silence.memblock, &ss); - u->silence.index = 0; + u->sink->thread_info.max_request = u->out_hwbuf_size; - u->module = m; - m->userdata = u; + if (use_mmap) + u->out_mmap_memblocks = pa_xnew0(pa_memblock*, u->out_nfrags); + } - pa_modargs_free(ma); + if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) { + pa_bool_t do_close = TRUE; + u->mixer_devmask = 0; - /* - * 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) { - char *buf = pa_xnew(char, u->sample_size); - pa_read(u->fd, buf, u->sample_size, NULL); - pa_xfree(buf); + 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 = FALSE; + } + + 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 = FALSE; + } + } + + 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->source) - source_get_hw_volume(u->source); + if (u->sink) { + if (sink_new_data.volume_is_set) { + if (u->sink->set_volume) + u->sink->set_volume(u->sink); + } else { + if (u->sink->get_volume) + u->sink->get_volume(u->sink); + } + } + + if (u->source) { + if (source_new_data.volume_is_set) { + if (u->source->set_volume) + u->source->set_volume(u->source); + } else { + if (u->source->get_volume) + u->source->get_volume(u->source); + } + } + if (u->sink) - sink_get_hw_volume(u->sink); + pa_sink_put(u->sink); + if (u->source) + pa_source_put(u->source); + + pa_modargs_free(ma); return 0; fail: - if (fd >= 0) - close(fd); + + if (u) + pa__done(m); + else if (fd >= 0) + pa_close(fd); if (ma) pa_modargs_free(ma); - pa_xfree(name_buf); - return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - - assert(c); - assert(m); + + pa_assert(m); if (!(u = m->userdata)) return; - clear_up(u); - + 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->silence.memblock) - pa_memblock_unref(u->silence.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 index 59d91aa4..cd25b890 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -26,50 +26,60 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.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/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/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_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>") + "channel_map=<channel map>"); -#define DEFAULT_FIFO_NAME "/tmp/music.output" +#define DEFAULT_FILE_NAME "fifo_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; - - pa_sink *sink; - pa_iochannel *io; - pa_defer_event *defer_event; + int fd; pa_memchunk memchunk; - pa_module *module; + + pa_rtpoll_item *rtpoll_item; + + int write_type; }; static const char* const valid_modargs[] = { @@ -82,175 +92,273 @@ static const char* const valid_modargs[] = { NULL }; -static void do_write(struct userdata *u) { - ssize_t r; - void *p; - - assert(u); +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; - u->core->mainloop->defer_enable(u->defer_event, 0); - - if (!pa_iochannel_is_writable(u->io)) - return; + switch (code) { - pa_module_set_used(u->module, pa_sink_used_by(u->sink)); - - if (!u->memchunk.length) - if (pa_sink_render(u->sink, PIPE_BUF, &u->memchunk) < 0) - return; + case PA_SINK_MESSAGE_GET_LATENCY: { + size_t n = 0; + int l; - assert(u->memchunk.memblock); - assert(u->memchunk.length); +#ifdef TIOCINQ + if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0) + n = (size_t) l; +#endif - p = pa_memblock_acquire(u->memchunk.memblock); - - if ((r = pa_iochannel_write(u->io, (uint8_t*) p + u->memchunk.index, u->memchunk.length)) < 0) { - pa_memblock_release(u->memchunk.memblock); - pa_log("write(): %s", pa_cstrerror(errno)); - return; - } - pa_memblock_release(u->memchunk.memblock); - - u->memchunk.index += r; - u->memchunk.length -= r; - - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; + n += u->memchunk.length; + + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + return 0; + } } + + return pa_sink_process_msg(o, code, data, offset, chunk); } -static void notify_cb(pa_sink*s) { - struct userdata *u = s->userdata; - assert(s && u); +static void process_rewind(struct userdata *u) { + pa_assert(u); - if (pa_iochannel_is_writable(u->io)) - u->core->mainloop->defer_enable(u->defer_event, 1); + pa_log_debug("Rewind requested but not supported by pipe sink. Ignoring."); + u->sink->thread_info.rewind_nbytes = 0; } -static pa_usec_t get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - assert(s && u); +static int process_render(struct userdata *u) { + pa_assert(u); - return u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0; -} + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); -static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) { - struct userdata *u = userdata; - assert(u); - do_write(u); + pa_assert(u->memchunk.length > 0); + + for (;;) { + ssize_t l; + void *p; + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &u->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)); + return -1; + } + + } 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); + } + } + + return 0; + } } -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { +static void thread_func(void *userdata) { struct userdata *u = userdata; - assert(u); - do_write(u); + + 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) { + + if (u->sink->thread_info.rewind_nbytes > 0) + process_rewind(u); + + if (pollfd->revents) { + if (process_render(u) < 0) + goto fail; + + 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_core *c, pa_module*m) { - struct userdata *u = NULL; +int pa__init(pa_module*m) { + struct userdata *u; struct stat st; - const char *p; - int fd = -1; pa_sample_spec ss; pa_channel_map map; - pa_modargs *ma = NULL; - char *t; - - assert(c && m); - + pa_modargs *ma; + struct pollfd *pollfd; + pa_sink_new_data data; + + pa_assert(m); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments."); goto fail; } - ss = c->default_sample_spec; + 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"); + pa_log("Invalid sample format specification or channel map"); goto fail; } - - mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777); - if ((fd = open(p, O_RDWR)) < 0) { - pa_log("open('%s'): %s", p, pa_cstrerror(errno)); + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_memchunk_reset(&u->memchunk); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->write_type = 0; + + u->filename = pa_runtime_path(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_fd_set_cloexec(fd, 1); - - if (fstat(fd, &st) < 0) { - pa_log("fstat('%s'): %s", p, pa_cstrerror(errno)); + 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.", p); + pa_log("'%s' is not a FIFO.", u->filename); goto fail; } - u = pa_xmalloc0(sizeof(struct userdata)); - u->filename = pa_xstrdup(p); - u->core = c; - u->module = m; - m->userdata = u; - - if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink."); + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->filename); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Unix FIFO sink %s", u->filename); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); goto fail; } - u->sink->notify = notify_cb; - u->sink->get_latency = get_latency_cb; + + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", p)); - pa_xfree(t); - u->io = pa_iochannel_new(c->mainloop, -1, fd); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); - u->memchunk.memblock = NULL; - u->memchunk.length = 0; + 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; + } - u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u); - assert(u->defer_event); - c->mainloop->defer_enable(u->defer_event, 0); + pa_sink_put(u->sink); pa_modargs_free(ma); - + return 0; fail: if (ma) pa_modargs_free(ma); - - if (fd >= 0) - close(fd); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + 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); - - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - pa_iochannel_free(u->io); - u->core->mainloop->defer_free(u->defer_event); - - assert(u->filename); - unlink(u->filename); - pa_xfree(u->filename); - + 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 index 99f4f3b9..b0de34ca 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -26,48 +26,57 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.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/iochannel.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_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>") + "channel_map=<channel map>"); -#define DEFAULT_FIFO_NAME "/tmp/music.input" +#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; - - pa_source *source; - pa_iochannel *io; - pa_module *module; - pa_memchunk chunk; + int fd; + + pa_memchunk memchunk; + + pa_rtpoll_item *rtpoll_item; }; static const char* const valid_modargs[] = { @@ -80,150 +89,227 @@ static const char* const valid_modargs[] = { NULL }; -static void do_read(struct userdata *u) { - ssize_t r; - void *p; - pa_memchunk chunk; - - assert(u); +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int read_type = 0; - if (!pa_iochannel_is_readable(u->io)) - return; + pa_assert(u); - pa_module_set_used(u->module, pa_idxset_size(u->source->outputs)); + pa_log_debug("Thread starting up"); - if (!u->chunk.memblock) { - u->chunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF); - u->chunk.index = chunk.length = 0; - } + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); - assert(u->chunk.memblock); - assert(pa_memblock_get_length(u->chunk.memblock) > u->chunk.index); + for (;;) { + int ret; + struct pollfd *pollfd; - p = pa_memblock_acquire(u->chunk.memblock); - if ((r = pa_iochannel_read(u->io, (uint8_t*) p + u->chunk.index, pa_memblock_get_length(u->chunk.memblock) - u->chunk.index)) <= 0) { - pa_memblock_release(u->chunk.memblock); - pa_log("read(): %s", pa_cstrerror(errno)); - return; - } - pa_memblock_release(u->chunk.memblock); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - u->chunk.length = r; - pa_source_post(u->source, &u->chunk); - u->chunk.index += r; + /* 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->chunk.index >= pa_memblock_get_length(u->chunk.memblock)) { - u->chunk.index = u->chunk.length = 0; - pa_memblock_unref(u->chunk.memblock); - u->chunk.memblock = NULL; + 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, TRUE)) < 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; + } } -} -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_read(u); +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_core *c, pa_module*m) { - struct userdata *u = NULL; +int pa__init(pa_module*m) { + struct userdata *u; struct stat st; - const char *p; - int fd = -1; pa_sample_spec ss; pa_channel_map map; - pa_modargs *ma = NULL; - char *t; - - assert(c && m); - + pa_modargs *ma; + struct pollfd *pollfd; + pa_source_new_data data; + + pa_assert(m); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("failed to parse module arguments."); goto fail; } - ss = c->default_sample_spec; + 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; } - - mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777); - if ((fd = open(p, O_RDWR)) < 0) { - pa_log("open('%s'): %s", p, pa_cstrerror(errno)); + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_memchunk_reset(&u->memchunk); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + u->filename = pa_runtime_path(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_fd_set_cloexec(fd, 1); - - if (fstat(fd, &st) < 0) { - pa_log("fstat('%s'): %s", p, pa_cstrerror(errno)); + 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.", p); + pa_log("'%s' is not a FIFO.", u->filename); goto fail; } - u = pa_xmalloc0(sizeof(struct userdata)); + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->filename); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Unix FIFO source %s", u->filename); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); - u->filename = pa_xstrdup(p); - u->core = c; - - if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { - pa_log("failed to create source."); + u->source = pa_source_new(m->core, &data, 0); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log("Failed to create source."); goto fail; } + u->source->userdata = u; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", p)); - pa_xfree(t); - u->io = pa_iochannel_new(c->mainloop, fd, -1); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); - u->chunk.memblock = NULL; - u->chunk.index = u->chunk.length = 0; - - u->module = m; - m->userdata = u; + 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); - - if (fd >= 0) - close(fd); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - - if (u->chunk.memblock) - pa_memblock_unref(u->chunk.memblock); - - pa_source_disconnect(u->source); - pa_source_unref(u->source); - pa_iochannel_free(u->io); - - assert(u->filename); - unlink(u->filename); - pa_xfree(u->filename); - + + 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-position-event-sounds.c b/src/modules/module-position-event-sounds.c new file mode 100644 index 00000000..90e693a3 --- /dev/null +++ b/src/modules/module-position-event-sounds.c @@ -0,0 +1,165 @@ +/*** + 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/channelmap.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/sink-input.h> + +#include "module-position-event-sounds-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Position event sounds between L and R depending on the position on screen of the widget triggering them."); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + NULL +}; + +struct userdata { + pa_core *core; + pa_hook_slot *sink_input_fixate_hook_slot; +}; + +static pa_bool_t is_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 is_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_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *data, struct userdata *u) { + const char *hpos; + double f; + unsigned c; + char t[PA_CVOLUME_SNPRINT_MAX]; + + pa_assert(data); + + if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS))) + return PA_HOOK_OK; + + if (pa_atod(hpos, &f) < 0) { + pa_log_warn("Failed to parse "PA_PROP_EVENT_MOUSE_HPOS" property '%s'.", hpos); + return PA_HOOK_OK; + } + + if (f < 0.0 || f > 1.0) { + pa_log_warn("Property "PA_PROP_EVENT_MOUSE_HPOS" out of range %0.2f", f); + return PA_HOOK_OK; + } + + pa_log_debug("Positioning event sound '%s' at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + + if (!data->volume_is_set) { + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_set = TRUE; + } + + for (c = 0; c < data->sample_spec.channels; c++) { + + if (is_left(data->channel_map.map[c])) + data->volume.values[c] = + pa_sw_volume_multiply(data->volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * (1.0 - f))); + + if (is_right(data->channel_map.map[c])) + data->volume.values[c] = + pa_sw_volume_multiply(data->volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * f)); + } + + pa_log_debug("Final volume %s.", pa_cvolume_snprint(t, sizeof(t), &data->volume)); + + 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"); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (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; +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input_fixate_hook_slot) + pa_hook_slot_free(u->sink_input_fixate_hook_slot); + + pa_xfree(u); +} diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index df58958a..0c9529c3 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -1,18 +1,19 @@ -/* $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 @@ -26,7 +27,6 @@ #include <string.h> #include <errno.h> #include <stdio.h> -#include <assert.h> #include <unistd.h> #include <limits.h> @@ -40,10 +40,9 @@ #include <netinet/in.h> #endif -#include "../pulsecore/winsock.h" - #include <pulse/xmalloc.h> +#include <pulsecore/winsock.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> #include <pulsecore/socket-server.h> @@ -75,7 +74,7 @@ #else #include "module-simple-protocol-unix-symdef.h" #endif - PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION) +PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION); PA_MODULE_USAGE("rate=<sample rate> " "format=<sample format> " "channels=<number of channels> " @@ -83,22 +82,22 @@ "source=<source to connect to> " "playback=<enable playback?> " "record=<enable record?> " - SOCKET_USAGE) + SOCKET_USAGE); #elif defined(USE_PROTOCOL_CLI) - #include <pulsecore/protocol-cli.h> + #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 + #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) + 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 @@ -106,14 +105,14 @@ #define TCPWRAP_SERVICE "pulseaudio-http" #define IPV4_PORT 4714 #define UNIX_SOCKET "http" - #define MODULE_ARGUMENTS + #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) + 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 @@ -129,21 +128,21 @@ #endif #if defined(HAVE_CREDS) && !defined(USE_TCP_SOCKETS) - #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-group", "auth-group-enable", + #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 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_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION); PA_MODULE_USAGE("auth-anonymous=<don't check for cookies?> " "cookie=<path to cookie file> " AUTH_USAGE - SOCKET_USAGE) + SOCKET_USAGE); #elif defined(USE_PROTOCOL_ESOUND) #include <pulsecore/protocol-esound.h> #include <pulsecore/esound.h> @@ -151,7 +150,6 @@ #define protocol_free pa_protocol_esound_free #define TCPWRAP_SERVICE "esound" #define IPV4_PORT ESD_DEFAULT_PORT - #define UNIX_SOCKET ESD_UNIX_SOCKET_NAME #define MODULE_ARGUMENTS_COMMON "sink", "source", "auth-anonymous", "cookie", #ifdef USE_TCP_SOCKETS #include "module-esound-protocol-tcp-symdef.h" @@ -160,26 +158,27 @@ #endif #if defined(USE_TCP_SOCKETS) - #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-ip-acl", + #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_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) + SOCKET_USAGE); #else - #error "Broken build system" + #error "Broken build system" #endif -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_VERSION(PACKAGE_VERSION); static const char* const valid_modargs[] = { MODULE_ARGUMENTS @@ -202,10 +201,9 @@ struct userdata { #endif }; -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1; - struct userdata *u = NULL; #if defined(USE_TCP_SOCKETS) @@ -215,10 +213,9 @@ int pa__init(pa_core *c, pa_module*m) { #else pa_socket_server *s; int r; - char tmp[PATH_MAX]; #endif - assert(c && m); + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -236,57 +233,68 @@ int pa__init(pa_core *c, pa_module*m) { listen_on = pa_modargs_get_value(ma, "listen", NULL); if (listen_on) { - s_ipv6 = pa_socket_server_new_ipv6_string(c->mainloop, listen_on, port, TCPWRAP_SERVICE); - s_ipv4 = pa_socket_server_new_ipv4_string(c->mainloop, listen_on, port, TCPWRAP_SERVICE); + 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(c->mainloop, port, TCPWRAP_SERVICE); - s_ipv4 = pa_socket_server_new_ipv4_any(c->mainloop, port, TCPWRAP_SERVICE); + 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(c, s_ipv4, m, ma))) - pa_socket_server_unref(s_ipv4); - + u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma); if (s_ipv6) - if (!(u->protocol_ipv6 = protocol_new(c, s_ipv6, m, ma))) - pa_socket_server_unref(s_ipv6); + u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma); if (!u->protocol_ipv4 && !u->protocol_ipv6) goto fail; -#else + if (s_ipv6) + pa_socket_server_unref(s_ipv6); + if (s_ipv6) + pa_socket_server_unref(s_ipv4); - pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp)); - u->socket_path = pa_xstrdup(tmp); +#else #if defined(USE_PROTOCOL_ESOUND) +#if defined(USE_PER_USER_ESOUND_SOCKET) + u->socket_path = pa_sprintf_malloc("/tmp/.esd-%lu/socket", (unsigned long) getuid()); +#else + u->socket_path = pa_xstrdup("/tmp/.esd/socket"); +#endif + /* 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, c->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) { - pa_log("Failed to create socket directory: %s\n", pa_cstrerror(errno)); + + if (pa_make_secure_parent_dir(u->socket_path, pa_in_system_mode() ? 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; } -#endif - - if ((r = pa_unix_socket_remove_stale(tmp)) < 0) { - pa_log("Failed to remove stale UNIX socket '%s': %s", tmp, pa_cstrerror(errno)); + +#else + if (!(u->socket_path = pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET)))) { + pa_log("Failed to generate socket path."); goto fail; } - - if (r) - pa_log("Removed stale UNIX socket '%s'.", tmp); - - if (!(s = pa_socket_server_new_unix(c->mainloop, tmp))) +#endif + + if ((r = pa_unix_socket_remove_stale(u->socket_path)) < 0) { + pa_log("Failed to remove stale UNIX socket '%s': %s", u->socket_path, pa_cstrerror(errno)); goto fail; + } else if (r > 0) + pa_log_info("Removed stale UNIX socket '%s'.", u->socket_path); - if (!(u->protocol_unix = protocol_new(c, s, m, ma))) + if (!(s = pa_socket_server_new_unix(m->core->mainloop, u->socket_path))) goto fail; + if (!(u->protocol_unix = protocol_new(m->core, s, m, ma))) + goto fail; + + pa_socket_server_unref(s); + #endif m->userdata = u; @@ -309,32 +317,29 @@ fail: #else if (u->protocol_unix) protocol_free(u->protocol_unix); - - if (u->socket_path) - pa_xfree(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); + 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); + if (s) + pa_socket_server_unref(s); #endif - } goto finish; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - - assert(c); - assert(m); + + pa_assert(m); u = m->userdata; @@ -347,15 +352,14 @@ void pa__done(pa_core *c, pa_module*m) { if (u->protocol_unix) protocol_free(u->protocol_unix); -#if defined(USE_PROTOCOL_ESOUND) +#if defined(USE_PROTOCOL_ESOUND) && !defined(USE_PER_USER_ESOUND_SOCKET) if (u->socket_path) { char *p = pa_parent_dir(u->socket_path); rmdir(p); pa_xfree(p); } #endif - - + pa_xfree(u->socket_path); #endif diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c new file mode 100644 index 00000000..c87b1ece --- /dev/null +++ b/src/modules/module-remap-sink.c @@ -0,0 +1,429 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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> " + "remix=<remix channels?>"); + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink *sink, *master; + pa_sink_input *sink_input; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "master", + "master_channel_map", + "rate", + "format", + "channels", + "channel_map", + "remix", + 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; + + /* Get the latency of the master sink */ + if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + /* Add the latency internal to our sink input on top */ + usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + + *((pa_usec_t*) data) = usec; + 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_IS_LINKED(state) && + u->sink_input && + PA_SINK_INPUT_IS_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 void sink_request_rewind(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from I/O thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return -1; + + pa_sink_render(u->sink, nbytes, chunk); + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(nbytes > 0); + + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return; + + if (u->sink->thread_info.rewind_nbytes > 0) { + size_t amount; + + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes); + u->sink->thread_info.rewind_nbytes = 0; + + if (amount > 0) + pa_sink_process_rewind(u->sink, amount); + } +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_set_max_rewind(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_set_max_request(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_update_latency_range(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +} + +/* 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); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_detach_within_thread(u->sink); + pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); +} + +/* 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); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + 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); + + pa_sink_update_latency_range(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); +} + +/* 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_unlink(u->sink); + pa_sink_input_unlink(u->sink_input); + + pa_sink_unref(u->sink); + u->sink = NULL; + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_module_unload_request(u->module); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); + } +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map sink_map, stream_map; + pa_modargs *ma; + const char *k; + pa_sink *master; + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; + pa_bool_t remix = TRUE; + + 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 channel map"); + goto fail; + } + + if (stream_map.channels != ss.channels) { + pa_log("Number of channels doesn't match"); + goto fail; + } + + if (pa_channel_map_equal(&stream_map, &master->channel_map)) + pa_log_warn("No remapping configured, proceeding nonetheless!"); + + if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { + pa_log("Invalid boolean remix parameter"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->master = master; + u->sink = NULL; + u->sink_input = NULL; + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.remapped", master->name); + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &sink_map); + k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { + 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->update_requested_latency = sink_update_requested_latency; + u->sink->request_rewind = sink_request_rewind; + u->sink->userdata = u; + + 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(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + sink_input_data.sink = u->master; + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream"); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map); + + u->sink_input = pa_sink_input_new(m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE | (remix ? 0 : PA_SINK_INPUT_NO_REMIX)); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->userdata = u; + + pa_sink_put(u->sink); + 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) { + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + } + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + pa_xfree(u); +} diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 7aa205bd..cc6717cb 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -1,18 +1,18 @@ -/* $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 @@ -34,9 +34,10 @@ #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_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, @@ -49,73 +50,86 @@ struct userdata { static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { pa_sink_input *i; pa_sink *target; - - assert(c); - assert(sink); + + 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))) { - pa_log_info("No evacuation sink found."); - return PA_HOOK_OK; - } - assert(target != sink); + 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); + if (pa_sink_input_move_to(i, target) < 0) { + pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); return PA_HOOK_OK; } - pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, i->name, target->name); + pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_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; - - assert(c); - assert(source); + + 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))) { - pa_log_info("No evacuation source found."); - 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; + } } - assert(target != source); + 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); + pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); return PA_HOOK_OK; } - pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, o->name, target->name); + pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); } - + return PA_HOOK_OK; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - - assert(c); - assert(m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -123,18 +137,17 @@ int pa__init(pa_core *c, pa_module*m) { } m->userdata = u = pa_xnew(struct userdata, 1); - u->sink_slot = pa_hook_connect(&c->hook_sink_disconnect, (pa_hook_cb_t) sink_hook_callback, NULL); - u->source_slot = pa_hook_connect(&c->hook_source_disconnect, (pa_hook_cb_t) source_hook_callback, NULL); + u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_hook_callback, NULL); + u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_hook_callback, NULL); pa_modargs_free(ma); return 0; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - - assert(c); - assert(m); + + pa_assert(m); if (!m->userdata) return; diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index f65b1f3a..38780f24 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <math.h> #include <pulse/xmalloc.h> @@ -34,13 +33,15 @@ #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_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>") -PA_MODULE_VERSION(PACKAGE_VERSION) +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; @@ -56,60 +57,80 @@ static const char* const valid_modargs[] = { NULL, }; -static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) { +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { struct userdata *u; - assert(i && chunk && i->userdata); - u = i->userdata; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + 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; + chunk->index = u->peek_index; + + u->peek_index = 0; + return 0; } -static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + size_t l; struct userdata *u; - assert(i && chunk && length && i->userdata); - u = i->userdata; - assert(chunk->memblock == u->memblock); - assert(length <= pa_memblock_get_length(u->memblock)-u->peek_index); + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); - u->peek_index += length; + l = pa_memblock_get_length(u->memblock); + nbytes %= l; - if (u->peek_index >= pa_memblock_get_length(u->memblock)) - u->peek_index = 0; + if (u->peek_index >= nbytes) + u->peek_index -= nbytes; + else + u->peek_index = l + u->peek_index - nbytes; } -static void sink_input_kill(pa_sink_input *i) { +static void sink_input_kill_cb(pa_sink_input *i) { struct userdata *u; - assert(i && i->userdata); - u = i->userdata; - pa_sink_input_disconnect(u->sink_input); + 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_module_unload_request(u->module); } +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); +} + 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_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; pa_sink *sink; - const char *sink_name; pa_sample_spec ss; uint32_t frequency; - char t[256]; void *p; pa_sink_input_new_data data; @@ -117,16 +138,15 @@ int pa__init(pa_core *c, pa_module*m) { pa_log("Failed to parse module arguments"); goto fail; } - - m->userdata = u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; + + 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; - sink_name = pa_modargs_get_value(ma, "sink", NULL); - - if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) { + 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; } @@ -140,56 +160,61 @@ int pa__init(pa_core *c, pa_module*m) { pa_log("Invalid frequency specification"); goto fail; } - - u->memblock = pa_memblock_new(c->mempool, pa_bytes_per_second(&ss)); + + 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); - - 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_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "%u Hz Sine", frequency); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency); pa_sink_input_new_data_set_sample_spec(&data, &ss); data.module = m; - if (!(u->sink_input = pa_sink_input_new(c, &data, 0))) + u->sink_input = pa_sink_input_new(m->core, &data, 0); + pa_sink_input_new_data_done(&data); + + if (!u->sink_input) goto fail; - u->sink_input->peek = sink_input_peek; - u->sink_input->drop = sink_input_drop; - u->sink_input->kill = sink_input_kill; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->userdata = u; - u->peek_index = 0; - + pa_sink_input_put(u->sink_input); + pa_modargs_free(ma); return 0; - + fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); return -1; } -void pa__done(pa_core *c, pa_module*m) { - struct userdata *u = m->userdata; - assert(c && m); +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); - if (!u) + if (!(u = m->userdata)) return; if (u->sink_input) { - pa_sink_input_disconnect(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 index 66968cb1..6f50543a 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,7 +26,6 @@ #include <stdlib.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> @@ -54,6 +54,9 @@ #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" @@ -72,12 +75,14 @@ PA_MODULE_USAGE( "channel_map=<channel map>") struct userdata { + pa_core *core; pa_sink *sink; pa_source *source; - pa_iochannel *io; - pa_core *core; - pa_time_event *timer; - pa_usec_t poll_timeout; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_signal_event *sig; pa_memchunk memchunk; @@ -87,9 +92,9 @@ struct userdata { uint32_t frame_size; uint32_t buffer_size; unsigned int written_bytes, read_bytes; - int sink_underflow; int fd; + pa_rtpoll_item *rtpoll_item; pa_module *module; }; @@ -111,309 +116,357 @@ static const char* const valid_modargs[] = { #define DEFAULT_SOURCE_NAME "solaris_input" #define DEFAULT_DEVICE "/dev/audio" -#define CHUNK_SIZE 2048 +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; -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)); -} + switch (code) { + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; -static void do_write(struct userdata *u) { - audio_info_t info; - int err; - size_t len; - ssize_t r; - - assert(u); + if (u->fd >= 0) { - /* We cannot check pa_iochannel_is_writable() because of our buffer hack */ - if (!u->sink) - return; + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); - update_usage(u); + 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); - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &PA_SINK(o)->sample_spec); + } - /* - * 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); + *((pa_usec_t*) data) = r; - /* The sample counter can sometimes go backwards :( */ - if (len > u->buffer_size) - len = 0; + return 0; + } - if (!u->sink_underflow && (len == u->buffer_size)) - pa_log_debug("Solaris buffer underflow!"); + 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; - len -= len % u->frame_size; + case PA_SINK_MESSAGE_GET_VOLUME: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + assert(err >= 0); - if (len == 0) - return; + pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels, + info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); - if (!u->memchunk.length) { - if (pa_sink_render(u->sink, len, &u->memchunk) < 0) { - u->sink_underflow = 1; - return; + return 0; } - } + break; - u->sink_underflow = 0; - - assert(u->memchunk.memblock); - assert(u->memchunk.memblock->data); - assert(u->memchunk.length); + case PA_SINK_MESSAGE_SET_MUTE: + if (u->fd >= 0) { + AUDIO_INITINFO(&info); - if (u->memchunk.length < len) { - len = u->memchunk.length; - len -= len % u->frame_size; - assert(len); - } + info.output_muted = !!PA_PTR_TO_UINT(data); - if ((r = pa_iochannel_write(u->io, - (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, len)) < 0) { - pa_log("write() failed: %s", pa_cstrerror(errno)); - return; - } + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + else + return 0; + } + break; - 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); - u->memchunk.memblock = NULL; + 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; } - u->written_bytes += r; + return pa_sink_process_msg(o, code, data, offset, chunk); } -static void do_read(struct userdata *u) { - pa_memchunk memchunk; +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; - size_t l; - ssize_t r; - assert(u); - - if (!u->source || !pa_iochannel_is_readable(u->io)) - return; + audio_info_t info; - update_usage(u); + switch (code) { + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; - err = ioctl(u->fd, I_NREAD, &l); - assert(err >= 0); + if (u->fd) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 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; + 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); + } - memchunk.memblock = pa_memblock_new(u->core->mempool, l); - assert(memchunk.memblock); - if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) { - pa_memblock_unref(memchunk.memblock); - if (errno != EAGAIN) - pa_log("read() failed: %s", pa_cstrerror(errno)); - return; - } - - assert(r <= (ssize_t) memchunk.memblock->length); - memchunk.length = memchunk.memblock->length = r; - memchunk.index = 0; - - pa_source_post(u->source, &memchunk); - pa_memblock_unref(memchunk.memblock); - - u->read_bytes += r; -} + *((pa_usec_t*) data) = r; -static void io_callback(pa_iochannel *io, void*userdata) { - struct userdata *u = userdata; - assert(u); - do_write(u); - do_read(u); -} + return 0; + } -static void timer_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) { - struct userdata *u = userdata; - struct timeval ntv; + 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; - assert(u); + case PA_SOURCE_MESSAGE_GET_VOLUME: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); - do_write(u); + pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels, + info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); - pa_gettimeofday(&ntv); - pa_timeval_add(&ntv, u->poll_timeout); + return 0; + } + break; + } - a->time_restart(e, &ntv); + return pa_source_process_msg(o, code, data, offset, chunk); } -static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) { - struct userdata *u = userdata; - pa_cvolume old_vol; - - assert(u); +static void clear_underflow(struct userdata *u) +{ + audio_info_t info; - if (u->sink) { - assert(u->sink->get_hw_volume); - memcpy(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume)); - if (u->sink->get_hw_volume(u->sink) < 0) - return; - if (memcmp(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume)) != 0) { - pa_subscription_post(u->sink->core, - PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, - u->sink->index); - } - } + AUDIO_INITINFO(&info); - if (u->source) { - assert(u->source->get_hw_volume); - memcpy(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume)); - if (u->source->get_hw_volume(u->source) < 0) - return; - if (memcmp(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume)) != 0) { - pa_subscription_post(u->source->core, - PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, - u->source->index); - } - } + info.play.error = 0; + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); } -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - pa_usec_t r = 0; +static void clear_overflow(struct userdata *u) +{ audio_info_t info; - int err; - struct userdata *u = s->userdata; - assert(s && u && u->sink); - - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); - r += pa_bytes_to_usec(u->written_bytes, &s->sample_spec); - r -= pa_bytes_to_usec(info.play.samples * u->frame_size, &s->sample_spec); + AUDIO_INITINFO(&info); - if (u->memchunk.memblock) - r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec); + info.record.error = 0; - return r; + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); } -static pa_usec_t source_get_latency_cb(pa_source *s) { - pa_usec_t r = 0; - struct userdata *u = s->userdata; - audio_info_t info; - int err; - assert(s && u && u->source); +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + int ret; - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + pa_assert(u); - r += pa_bytes_to_usec(info.record.samples * u->frame_size, &s->sample_spec); - r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec); + pa_log_debug("Thread starting up"); - return r; -} + if (u->core->high_priority) + pa_make_realtime(); -static int sink_get_hw_volume_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; - int err; + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + for (;;) { + /* Render some data and write it to the dsp */ - pa_cvolume_set(&s->hw_volume, s->hw_volume.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) { + audio_info_t info; + int err; + size_t len; - return 0; -} + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); -static int sink_set_hw_volume_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; + /* + * 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); - AUDIO_INITINFO(&info); + /* The sample counter can sometimes go backwards :( */ + if (len > u->buffer_size) + len = 0; - info.play.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; - assert(info.play.gain <= AUDIO_MAX_GAIN); + if (info.play.error) { + pa_log_debug("Solaris buffer underflow!"); + clear_underflow(u); + } - 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)); - return -1; - } + len -= len % u->frame_size; - return 0; -} + while (len) { + void *p; + ssize_t r; -static int sink_get_hw_mute_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; - int err; + if (!u->memchunk.length) + pa_sink_render(u->sink, len, &u->memchunk); - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + pa_assert(u->memchunk.length); - s->hw_muted = !!info.output_muted; + 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); - return 0; -} + 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); -static int sink_set_hw_mute_cb(pa_sink *s) { - struct userdata *u = s->userdata; - audio_info_t info; + u->memchunk.index += r; + u->memchunk.length -= r; - AUDIO_INITINFO(&info); + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } - info.output_muted = !!s->hw_muted; + len -= r; + u->written_bytes += r; + } + } + } - if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { - pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); - return -1; - } + /* 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; + } + } + } - return 0; -} + if (u->fd >= 0) { + struct pollfd *pollfd; -static int source_get_hw_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; - audio_info_t info; - int err; + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->events = + ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0); + } - err = ioctl(u->fd, AUDIO_GETINFO, &info); - assert(err >= 0); + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; - pa_cvolume_set(&s->hw_volume, s->hw_volume.channels, - info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + if (ret == 0) + goto finish; - return 0; -} + if (u->fd >= 0) { + struct pollfd *pollfd; -static int source_set_hw_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; - audio_info_t info; + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - AUDIO_INITINFO(&info); + if (pollfd->revents & ~(POLLOUT|POLLIN)) { + pa_log("DSP shutdown."); + goto fail; + } - info.record.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; - assert(info.record.gain <= AUDIO_MAX_GAIN); + revents = pollfd->revents; + } else + revents = 0; + } - 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)); - return -1; +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); } - return 0; + if (u->source) + pa_source_get_volume(u->source); } static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) { @@ -487,6 +540,7 @@ static int pa_solaris_set_buffer(int fd, int buffer_size) { AUDIO_INITINFO(&info); + info.play.buffer_size = buffer_size; info.record.buffer_size = buffer_size; if (ioctl(fd, AUDIO_SETINFO, &info) < 0) { @@ -500,7 +554,7 @@ static int pa_solaris_set_buffer(int fd, int buffer_size) { return 0; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module *m) { struct userdata *u = NULL; const char *p; int fd = -1; @@ -510,15 +564,16 @@ int pa__init(pa_core *c, pa_module*m) { pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; - struct timeval tv; char *t; - assert(c && m); + 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; @@ -531,18 +586,18 @@ int pa__init(pa_core *c, pa_module*m) { mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); - buffer_size = 16384; + 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 = c->default_sample_spec; + 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; @@ -551,55 +606,18 @@ int pa__init(pa_core *c, pa_module*m) { if (pa_solaris_auto_format(fd, mode, &ss) < 0) goto fail; - if ((mode != O_WRONLY) && (buffer_size >= 1)) - if (pa_solaris_set_buffer(fd, buffer_size) < 0) - goto fail; + if (pa_solaris_set_buffer(fd, buffer_size) < 0) + goto fail; u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; - - if (mode != O_WRONLY) { - 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->get_latency = source_get_latency_cb; - u->source->get_hw_volume = source_get_hw_volume_cb; - u->source->set_hw_volume = source_set_hw_volume_cb; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); - pa_xfree(t); - u->source->is_hardware = 1; - } else - u->source = NULL; + u->core = m->core; - if (mode != O_RDONLY) { - 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->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->get_hw_mute = sink_get_hw_mute_cb; - u->sink->set_hw_mute = sink_set_hw_mute_cb; - u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); - pa_xfree(t); - u->sink->is_hardware = 1; - } else - u->sink = NULL; - - assert(u->source || u->sink); - - u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : 0); - assert(u->io); - pa_iochannel_set_callback(u->io, io_callback, u); u->fd = fd; - u->memchunk.memblock = NULL; - u->memchunk.length = 0; + pa_memchunk_reset(&u->memchunk); /* We use this to get a reasonable chunk size */ - u->page_size = sysconf(_SC_PAGESIZE); + u->page_size = PA_PAGE_SIZE; u->frame_size = pa_frame_size(&ss); u->buffer_size = buffer_size; @@ -607,70 +625,140 @@ int pa__init(pa_core *c, pa_module*m) { u->written_bytes = 0; u->read_bytes = 0; - u->sink_underflow = 1; - u->module = m; m->userdata = u; - u->poll_timeout = pa_bytes_to_usec(u->buffer_size / 10, &ss); + 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_gettimeofday(&tv); - pa_timeval_add(&tv, u->poll_timeout); + pa_rtpoll_set_timer_periodic(u->rtpoll, pa_bytes_to_usec(u->buffer_size / 10, &ss)); - u->timer = c->mainloop->time_new(c->mainloop, &tv, timer_cb, u); - assert(u->timer); + 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); - assert(u->sig); + pa_assert(u->sig); ioctl(u->fd, I_SETSIG, S_MSG); - pa_modargs_free(ma); + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } /* Read mixer settings */ if (u->source) - source_get_hw_volume_cb(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) { - sink_get_hw_volume_cb(u->sink); - sink_get_hw_mute_cb(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 (fd >= 0) + if (u) + pa__done(m); + else if (fd >= 0) close(fd); if (ma) pa_modargs_free(ma); - + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module *m) { struct userdata *u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - if (u->timer) - c->mainloop->time_free(u->timer); ioctl(u->fd, I_SETSIG, 0); pa_signal_free(u->sig); - - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); + 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); } - - if (u->source) { - pa_source_disconnect(u->source); + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->source) pa_source_unref(u->source); - } - - pa_iochannel_free(u->io); + + 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..bc7c023c --- /dev/null +++ b/src/modules/module-suspend-on-idle.c @@ -0,0 +1,444 @@ +/*** + 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_IS_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_IS_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_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) device_new_hook_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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_NORMAL, (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 index a110c57e..86f30817 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,7 +25,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <sys/types.h> @@ -49,10 +49,21 @@ #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> +#include <pulsecore/proplist-util.h> #ifdef TUNNEL_SINK #include "module-tunnel-sink-symdef.h" -PA_MODULE_DESCRIPTION("Tunnel module for sinks") +#else +#include "module-tunnel-source-symdef.h" +#endif + +#ifdef TUNNEL_SINK +PA_MODULE_DESCRIPTION("Tunnel module for sinks"); PA_MODULE_USAGE( "server=<address> " "sink=<remote sink name> " @@ -61,10 +72,9 @@ PA_MODULE_USAGE( "channels=<number of channels> " "rate=<sample rate> " "sink_name=<name for the local sink> " - "channel_map=<channel map>") + "channel_map=<channel map>"); #else -#include "module-tunnel-source-symdef.h" -PA_MODULE_DESCRIPTION("Tunnel module for sources") +PA_MODULE_DESCRIPTION("Tunnel module for sources"); PA_MODULE_USAGE( "server=<address> " "source=<remote source name> " @@ -73,21 +83,12 @@ PA_MODULE_USAGE( "channels=<number of channels> " "rate=<sample rate> " "source_name=<name for the local source> " - "channel_map=<channel map>") + "channel_map=<channel map>"); #endif -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_VERSION(PACKAGE_VERSION) - -#define DEFAULT_TLENGTH (44100*2*2/10) //(10240*8) -#define DEFAULT_MAXLENGTH ((DEFAULT_TLENGTH*3)/2) -#define DEFAULT_MINREQ 512 -#define DEFAULT_PREBUF (DEFAULT_TLENGTH-DEFAULT_MINREQ) -#define DEFAULT_FRAGSIZE 1024 - -#define DEFAULT_TIMEOUT 5 - -#define LATENCY_INTERVAL 10 +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); static const char* const valid_modargs[] = { "server", @@ -106,23 +107,70 @@ static const char* const valid_modargs[] = { NULL, }; -static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); -static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +#define DEFAULT_TIMEOUT 5 + +#define LATENCY_INTERVAL 10 + +#define MIN_NETWORK_LATENCY_USEC (8*PA_USEC_PER_MSEC) + +#ifdef TUNNEL_SINK + +enum { + SINK_MESSAGE_REQUEST = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_REMOTE_SUSPEND, + SINK_MESSAGE_UPDATE_LATENCY, + SINK_MESSAGE_POST +}; + +#define DEFAULT_TLENGTH_MSEC 150 +#define DEFAULT_MINREQ_MSEC 25 + +#else + +enum { + SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX, + SOURCE_MESSAGE_REMOTE_SUSPEND, + SOURCE_MESSAGE_UPDATE_LATENCY +}; + +#define DEFAULT_FRAGSIZE_MSEC 25 + +#endif #ifdef TUNNEL_SINK static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_started(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_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_suspended(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_STARTED] = command_started, +#endif + [PA_COMMAND_SUBSCRIBE_EVENT] = command_subscribe_event, + [PA_COMMAND_OVERFLOW] = command_overflow_or_underflow, + [PA_COMMAND_UNDERFLOW] = command_overflow_or_underflow, [PA_COMMAND_PLAYBACK_STREAM_KILLED] = command_stream_killed, [PA_COMMAND_RECORD_STREAM_KILLED] = command_stream_killed, - [PA_COMMAND_SUBSCRIBE_EVENT] = command_subscribe_event, + [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = command_suspended, + [PA_COMMAND_RECORD_STREAM_SUSPENDED] = command_suspended, + [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; @@ -131,14 +179,11 @@ struct userdata { #ifdef TUNNEL_SINK char *sink_name; pa_sink *sink; - uint32_t requested_bytes; + int32_t requested_bytes; #else char *source_name; pa_source *source; #endif - - pa_module *module; - pa_core *core; uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; @@ -146,225 +191,589 @@ struct userdata { uint32_t ctag; uint32_t device_index; uint32_t channel; - - pa_usec_t host_latency; - pa_time_event *time_event; + int64_t counter, counter_delta; - int auth_cookie_in_property; -}; + pa_bool_t remote_corked:1; + pa_bool_t remote_suspended:1; -static void close_stuff(struct userdata *u) { - assert(u); - - if (u->pstream) { - pa_pstream_close(u->pstream); - pa_pstream_unref(u->pstream); - u->pstream = NULL; - } + pa_usec_t transport_usec; + pa_bool_t transport_usec_valid; - if (u->pdispatch) { - pa_pdispatch_unref(u->pdispatch); - u->pdispatch = NULL; - } + uint32_t ignore_latency_before; - if (u->client) { - pa_socket_client_unref(u->client); - u->client = NULL; - } + 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 - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - } + uint32_t tlength; + uint32_t minreq; + uint32_t prebuf; #else - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - } + uint32_t fragsize; #endif +}; - if (u->time_event) { - u->core->mainloop->time_free(u->time_event); - u->time_event = NULL; - } -} +static void request_latency(struct userdata *u); + +/* Called from main context */ +static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; -static void die(struct userdata *u) { - assert(u); - close_stuff(u); + 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_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; - assert(pd && t && u && u->pdispatch == pd); - pa_log("stream killed"); - die(u); -} + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); -static void request_info(struct userdata *u); + pa_log_info("Server signalled buffer overrun/underrun."); + request_latency(u); +} -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) { +/* Called from main context */ +static void command_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; - pa_subscription_event_type_t e; - uint32_t idx; + uint32_t channel; + pa_bool_t suspended; - assert(pd && t && u); - assert(command == PA_COMMAND_SUBSCRIBE_EVENT); + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); - if (pa_tagstruct_getu32(t, &e) < 0 || - pa_tagstruct_getu32(t, &idx) < 0 || + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0 || !pa_tagstruct_eof(t)) { - pa_log("invalid protocol reply"); - die(u); + pa_log("Invalid packet"); + pa_module_unload_request(u->module); return; } #ifdef TUNNEL_SINK - if (e != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE)) - return; + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); #else - if (e != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) - return; + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); #endif - request_info(u); + request_latency(u); +} + +/* Called from main context */ +static void command_moved(pa_pdispatch *pd, uint32_t command, 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."); + request_latency(u); } #ifdef TUNNEL_SINK -static void send_prebuf_request(struct userdata *u) { + +/* Called from main context */ +static void command_started(pa_pdispatch *pd, uint32_t command, 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 playback started."); + request_latency(u); +} + +#endif + +/* Called from IO thread context */ +static void stream_cork_within_thread(struct userdata *u, pa_bool_t cork) { + pa_usec_t x; + pa_assert(u); + + if (u->remote_corked == cork) + return; + + u->remote_corked = cork; + x = pa_rtclock_usec(); + + /* Correct by the time this needs to travel to the other side. + * This is a valid thread-safe access, because the main thread is + * waiting for us */ + if (u->transport_usec_valid) + x += u->transport_usec; + + if (u->remote_suspended || u->remote_corked) + pa_smoother_pause(u->smoother, x); + else + pa_smoother_resume(u->smoother, x); +} + +/* Called from main context */ +static void stream_cork(struct userdata *u, pa_bool_t cork) { pa_tagstruct *t; + pa_assert(u); + + if (!u->pstream) + return; t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_PREBUF_PLAYBACK_STREAM); +#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); + + request_latency(u); } -static void send_bytes(struct userdata *u) { - assert(u); +/* Called from IO thread context */ +static void stream_suspend_within_thread(struct userdata *u, pa_bool_t suspend) { + pa_usec_t x; + pa_assert(u); - if (!u->pstream) + if (u->remote_suspended == suspend) return; + u->remote_suspended = suspend; + + x = pa_rtclock_usec(); + + /* Correct by the time this needed to travel from the other side. + * This is a valid thread-safe access, because the main thread is + * waiting for us */ + if (u->transport_usec_valid) + x -= u->transport_usec; + + if (u->remote_suspended || u->remote_corked) + pa_smoother_pause(u->smoother, x); + else + pa_smoother_resume(u->smoother, x); +} + +#ifdef TUNNEL_SINK + +/* Called from IO thread context */ +static void send_data(struct userdata *u) { + pa_assert(u); + while (u->requested_bytes > 0) { - pa_memchunk chunk; - if (pa_sink_render(u->sink, u->requested_bytes, &chunk) < 0) { - - if (u->requested_bytes >= DEFAULT_TLENGTH-DEFAULT_PREBUF) - send_prebuf_request(u); - - return; + 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; + + u->counter += 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) { + + stream_cork_within_thread(u, u->sink->state == PA_SINK_SUSPENDED); + + if (PA_SINK_IS_OPENED(u->sink->state)) + send_data(u); + } + + return r; } - pa_pstream_send_memblock(u->pstream, u->channel, 0, PA_SEEK_RELATIVE, &chunk); - pa_memblock_unref(chunk.memblock); + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t yl, yr, *usec = data; -/* pa_log("sent %lu", (unsigned long) chunk.length); */ + yl = pa_bytes_to_usec(u->counter, &u->sink->sample_spec); + yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); - if (chunk.length > u->requested_bytes) - u->requested_bytes = 0; - else - u->requested_bytes -= chunk.length; + *usec = yl > yr ? yl - yr : 0; + return 0; + } + + case SINK_MESSAGE_REQUEST: + + pa_assert(offset > 0); + u->requested_bytes += (size_t) offset; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) + send_data(u); + + return 0; + + + case SINK_MESSAGE_REMOTE_SUSPEND: + + stream_suspend_within_thread(u, !!PA_PTR_TO_UINT(data)); + return 0; + + + case SINK_MESSAGE_UPDATE_LATENCY: { + pa_usec_t y; + + y = pa_bytes_to_usec(u->counter, &u->sink->sample_spec); + + if (y > (pa_usec_t) offset || offset < 0) + y -= offset; + else + y = 0; + + pa_smoother_put(u->smoother, pa_rtclock_usec(), y); + + 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_delta += chunk->length; + + 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); + u = s->userdata; + + switch ((pa_sink_state_t) state) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_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 + +/* This function is called from IO context -- except when it is not. */ +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_SINK_MESSAGE_SET_STATE: { + int r; + + if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0) + stream_cork_within_thread(u, u->source->state == PA_SOURCE_SUSPENDED); + + return r; + } + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t yr, yl, *usec = data; + + yl = pa_bytes_to_usec(u->counter, &PA_SINK(o)->sample_spec); + yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); + + *usec = yr > yl ? yr - yl : 0; + return 0; + } + + case SOURCE_MESSAGE_POST: + + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) + pa_source_post(u->source, chunk); + + u->counter += chunk->length; + + return 0; + + case SOURCE_MESSAGE_REMOTE_SUSPEND: + + stream_suspend_within_thread(u, !!PA_PTR_TO_UINT(data)); + return 0; + + case SOURCE_MESSAGE_UPDATE_LATENCY: { + pa_usec_t y; + + y = pa_bytes_to_usec(u->counter, &u->source->sample_spec); + + if (offset >= 0 || y > (pa_usec_t) -offset) + y += offset; + else + y = 0; + + pa_smoother_put(u->smoother, pa_rtclock_usec(), y); + + return 0; + } + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +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_IS_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"); } -static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +#ifdef TUNNEL_SINK +/* Called from main context */ +static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; uint32_t bytes, channel; - assert(pd && command == PA_COMMAND_REQUEST && t && u && u->pdispatch == pd); + + 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_tagstruct_eof(t)) { - pa_log("invalid protocol reply"); - die(u); - return; + pa_tagstruct_getu32(t, &bytes) < 0) { + pa_log("Invalid protocol reply"); + goto fail; } if (channel != u->channel) { - pa_log("recieved data for invalid channel"); - die(u); - return; + pa_log("Recieved data for invalid channel"); + goto fail; } - - u->requested_bytes += bytes; - send_bytes(u); + + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL, 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) { +/* Called from main context */ +static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; pa_usec_t sink_usec, source_usec, transport_usec; - int playing; + pa_bool_t playing; int64_t write_index, read_index; struct timeval local, remote, now; - assert(pd && u); + pa_sample_spec *ss; + int64_t delay; + + pa_assert(pd); + pa_assert(u); if (command != PA_COMMAND_REPLY) { if (command == PA_COMMAND_ERROR) - pa_log("failed to get latency."); + pa_log("Failed to get latency."); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + 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_tagstruct_eof(t)) { - pa_log("invalid reply. (latency)"); - die(u); + pa_tagstruct_gets64(t, &read_index) < 0) { + pa_log("Invalid reply."); + goto fail; + } + +#ifdef TUNNEL_SINK + if (u->version >= 13) { + uint64_t underrun_for = 0, playing_for = 0; + + if (pa_tagstruct_getu64(t, &underrun_for) < 0 || + pa_tagstruct_getu64(t, &playing_for) < 0) { + pa_log("Invalid reply."); + goto fail; + } + } +#endif + + if (!pa_tagstruct_eof(t)) { + pa_log("Invalid reply."); + goto fail; + } + + if (tag < u->ignore_latency_before) { + request_latency(u); return; } pa_gettimeofday(&now); - /* FIXME! This could use some serious love. */ - + /* Calculate transport usec */ 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); + u->transport_usec = pa_timeval_diff(&remote, &local); #else - transport_usec = pa_timeval_diff(&now, &remote); -#endif + u->transport_usec = pa_timeval_diff(&now, &remote); +#endif } else - transport_usec = pa_timeval_diff(&now, &local)/2; + u->transport_usec = pa_timeval_diff(&now, &local)/2; + u->transport_usec_valid = TRUE; + /* First, take the device's delay */ #ifdef TUNNEL_SINK - u->host_latency = sink_usec + transport_usec; + delay = (int64_t) sink_usec; + ss = &u->sink->sample_spec; #else - u->host_latency = source_usec + transport_usec; - if (u->host_latency > sink_usec) - u->host_latency -= sink_usec; + delay = (int64_t) source_usec; + ss = &u->source->sample_spec; +#endif + + /* Add the length of our server-side buffer */ + if (write_index >= read_index) + delay += (int64_t) pa_bytes_to_usec(write_index-read_index, ss); else - u->host_latency = 0; + delay -= (int64_t) pa_bytes_to_usec(read_index-write_index, ss); + + /* Our measurements are already out of date, hence correct by the * + * transport latency */ +#ifdef TUNNEL_SINK + delay -= (int64_t) transport_usec; +#else + delay += (int64_t) transport_usec; #endif -/* pa_log("estimated host latency: %0.0f usec", (double) u->host_latency); */ + /* Now correct by what we have have read/written since we requested the update */ +#ifdef TUNNEL_SINK + delay += (int64_t) pa_bytes_to_usec(u->counter_delta, ss); +#else + delay -= (int64_t) pa_bytes_to_usec(u->counter_delta, ss); +#endif + +#ifdef TUNNEL_SINK + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UPDATE_LATENCY, 0, delay, NULL); +#else + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_UPDATE_LATENCY, 0, delay, NULL); +#endif + + return; + +fail: + + pa_module_unload_request(u->module); } +/* Called from main context */ static void request_latency(struct userdata *u) { pa_tagstruct *t; struct timeval now; uint32_t tag; - assert(u); + pa_assert(u); t = pa_tagstruct_new(NULL, 0); -#ifdef TUNNEL_SINK +#ifdef TUNNEL_SINK pa_tagstruct_putu32(t, PA_COMMAND_GET_PLAYBACK_LATENCY); #else pa_tagstruct_putu32(t, PA_COMMAND_GET_RECORD_LATENCY); @@ -374,37 +783,157 @@ static void request_latency(struct userdata *u) { 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->ignore_latency_before = tag; + u->counter_delta = 0; } -static void stream_get_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) { struct userdata *u = userdata; - uint32_t idx, owner_module, monitor_source; - pa_usec_t latency; + 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); +} + +/* Called from main context */ +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); + pa_proplist_sets(u->sink->proplist, "tunnel.remote.user", u->user_name); + pa_proplist_sets(u->sink->proplist, "tunnel.remote.fqdn", u->server_fqdn); + pa_proplist_sets(u->sink->proplist, "tunnel.remote.description", u->device_description); +#else + pa_source_set_description(u->source, d); + pa_proplist_sets(u->source->proplist, "tunnel.remote.user", u->user_name); + pa_proplist_sets(u->source->proplist, "tunnel.remote.fqdn", u->server_fqdn); + pa_proplist_sets(u->source->proplist, "tunnel.remote.description", u->device_description); +#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); +} + +/* Called from main context */ +static void server_info_cb(pa_pdispatch *pd, uint32_t command, 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."); + 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("Parse failure"); + goto fail; + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); + 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 + +/* Called from main context */ +static void sink_info_cb(pa_pdispatch *pd, uint32_t command, 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; - int mute; - uint32_t flags; - pa_sample_spec sample_spec; - pa_channel_map channel_map; + pa_sample_spec ss; + pa_channel_map cm; pa_cvolume volume; - assert(pd && u); + pa_bool_t mute; + pa_usec_t latency; + pa_proplist *pl; + + pa_assert(pd); + pa_assert(u); + + pl = pa_proplist_new(); if (command != PA_COMMAND_REPLY) { if (command == PA_COMMAND_ERROR) - pa_log("failed to get info."); + pa_log("Failed to get info."); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + 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, &sample_spec) < 0 || - pa_tagstruct_get_channel_map(t, &channel_map) < 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 || @@ -412,159 +941,411 @@ static void stream_get_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_ 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_tagstruct_eof(t)) { - pa_log("invalid reply. (get_info)"); - die(u); + pa_tagstruct_getu32(t, &flags) < 0) { + + pa_log("Parse failure"); + goto fail; + } + + if (u->version >= 13) { + pa_usec_t configured_latency; + + if (pa_tagstruct_get_proplist(t, pl) < 0 || + pa_tagstruct_get_usec(t, &configured_latency) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); + goto fail; + } + + pa_proplist_free(pl); + + if (!u->sink_name || 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); + pa_proplist_free(pl); +} + +/* Called from main context */ +static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, 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; + pa_bool_t mute; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_cvolume volume; + pa_proplist *pl; + + pa_assert(pd); + pa_assert(u); + + pl = pa_proplist_new(); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get info."); + else + pa_log("Protocol error."); + goto fail; } -#ifdef TUNNEL_SINK - assert(u->sink); - if ((!!mute == !!u->sink->hw_muted) && - pa_cvolume_equal(&volume, &u->sink->hw_volume)) + 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) { + + pa_log("Parse failure"); + goto fail; + } + + if (u->version >= 11) { + if (pa_tagstruct_get_boolean(t, &mute) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (u->version >= 13) { + if (pa_tagstruct_get_proplist(t, pl) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); + goto fail; + } + + pa_proplist_free(pl); + + if (idx != u->device_index) return; -#else - assert(u->source); - if ((!!mute == !!u->source->hw_muted) && - pa_cvolume_equal(&volume, &u->source->hw_volume)) + + pa_assert(u->sink); + + if ((u->version < 11 || !!mute == !!u->sink->muted) && + pa_cvolume_equal(&volume, &u->sink->volume)) return; -#endif -#ifdef TUNNEL_SINK - memcpy(&u->sink->hw_volume, &volume, sizeof(pa_cvolume)); - u->sink->hw_muted = !!mute; + 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); + pa_proplist_free(pl); +} - pa_subscription_post(u->sink->core, - PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, - u->sink->index); #else - memcpy(&u->source->hw_volume, &volume, sizeof(pa_cvolume)); - u->source->hw_muted = !!mute; - pa_subscription_post(u->source->core, - PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, - u->source->index); -#endif +/* Called from main context */ +static void source_info_cb(pa_pdispatch *pd, uint32_t command, 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; + pa_bool_t mute; + pa_usec_t latency, configured_latency; + pa_proplist *pl; + + pa_assert(pd); + pa_assert(u); + + pl = pa_proplist_new(); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get info."); + else + pa_log("Protocol error."); + 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("Parse failure"); + goto fail; + } + + if (u->version >= 13) { + if (pa_tagstruct_get_proplist(t, pl) < 0 || + pa_tagstruct_get_usec(t, &configured_latency) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); + goto fail; + } + + pa_proplist_free(pl); + + if (!u->source_name || 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); + pa_proplist_free(pl); } +#endif + +/* Called from main context */ static void request_info(struct userdata *u) { pa_tagstruct *t; uint32_t tag; - assert(u); + 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 - pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO); + 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); + + if (u->sink_name) { + 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 - pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO); + if (u->source_name) { + 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 - pa_tagstruct_putu32(t, tag = u->ctag++); +} + +/* Called from main context */ +static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, 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; + } - pa_tagstruct_putu32(t, PA_INVALID_INDEX); + if (e != (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE) && #ifdef TUNNEL_SINK - pa_tagstruct_puts(t, u->sink_name); + e != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + e != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) #else - pa_tagstruct_puts(t, u->source_name); + e != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) #endif + ) + return; - pa_pstream_send_tagstruct(u->pstream, t); - pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_info_callback, u, NULL); + request_info(u); } +/* Called from main context */ static void start_subscribe(struct userdata *u) { pa_tagstruct *t; uint32_t tag; - assert(u); + 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_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SINK); + PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK #else - pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SOURCE); + PA_SUBSCRIPTION_MASK_SOURCE #endif + ); pa_pstream_send_tagstruct(u->pstream, t); } -static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { +/* Called from main context */ +static void create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; struct timeval ntv; - assert(m && e && u); - - request_latency(u); - - pa_gettimeofday(&ntv); - ntv.tv_sec += LATENCY_INTERVAL; - m->time_restart(e, &ntv); -} +#ifdef TUNNEL_SINK + uint32_t bytes; +#endif -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; - assert(pd && u && u->pdispatch == pd); + 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."); + pa_log("Failed to create stream."); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + 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, &u->requested_bytes) < 0 -#endif +#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) + if (pa_tagstruct_getu32(t, &u->maxlength) < 0 || + pa_tagstruct_getu32(t, &u->tlength) < 0 || + pa_tagstruct_getu32(t, &u->prebuf) < 0 || + pa_tagstruct_getu32(t, &u->minreq) < 0) goto parse_error; #else - uint32_t maxlength, fragsize; - - if (pa_tagstruct_getu32(t, &maxlength) < 0 || - pa_tagstruct_getu32(t, &fragsize) < 0) + if (pa_tagstruct_getu32(t, &u->maxlength) < 0 || + pa_tagstruct_getu32(t, &u->fragsize) < 0) goto parse_error; #endif } - + + if (u->version >= 12) { + pa_sample_spec ss; + pa_channel_map cm; + uint32_t device_index; + const char *dn; + pa_bool_t suspended; + + if (pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || + pa_tagstruct_getu32(t, &device_index) < 0 || + pa_tagstruct_gets(t, &dn) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0) + goto parse_error; + +#ifdef TUNNEL_SINK + pa_xfree(u->sink_name); + u->sink_name = pa_xstrdup(dn); +#else + pa_xfree(u->source_name); + u->source_name = pa_xstrdup(dn); +#endif + } + + if (u->version >= 13) { + pa_usec_t usec; + + if (pa_tagstruct_get_usec(t, &usec) < 0) + goto parse_error; + +#ifdef TUNNEL_SINK + pa_sink_set_latency_range(u->sink, usec + MIN_NETWORK_LATENCY_USEC, 0); +#else + pa_source_set_latency_range(u->source, usec + MIN_NETWORK_LATENCY_USEC, 0); +#endif + } + if (!pa_tagstruct_eof(t)) goto parse_error; start_subscribe(u); request_info(u); - assert(!u->time_event); + 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 - send_bytes(u); + 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)"); - die(u); + pa_log("Invalid reply. (Create stream)"); + +fail: + pa_module_unload_request(u->module); + } +/* Called from main context */ 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; @@ -572,123 +1353,218 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t #ifdef TUNNEL_SINK pa_cvolume volume; #endif - assert(pd && u && u->pdispatch == pd); + + pa_assert(pd); + pa_assert(u); + pa_assert(u->pdispatch == pd); if (command != PA_COMMAND_REPLY || pa_tagstruct_getu32(t, &u->version) < 0 || !pa_tagstruct_eof(t)) { + if (command == PA_COMMAND_ERROR) - pa_log("failed to authenticate"); + pa_log("Failed to authenticate"); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + + goto fail; } /* Minimum supported protocol version */ if (u->version < 8) { - pa_log("incompatible protocol version"); - die(u); - return; + pa_log("Incompatible protocol version"); + goto fail; } + /* Starting with protocol version 13 the MSB of the version tag + reflects if shm is enabled for this connection or not. We don't + support SHM here at all, so we just ignore this. */ + + if (u->version >= 13) + u->version &= 0x7FFFFFFFU; + + pa_log_debug("Protocol version: remote %u, local %u", u->version, PA_PROTOCOL_VERSION); + #ifdef TUNNEL_SINK - snprintf(name, sizeof(name), "Tunnel from host %s, user %s, sink %s", - pa_get_host_name(hn, sizeof(hn)), - pa_get_user_name(un, sizeof(un)), - u->sink->name); + 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 - snprintf(name, sizeof(name), "Tunnel from host %s, user %s, source %s", - pa_get_host_name(hn, sizeof(hn)), - pa_get_user_name(un, sizeof(un)), - u->source->name); + 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, name); + + if (u->version >= 13) { + pa_proplist *pl; + pl = pa_proplist_new(); + pa_init_proplist(pl); + pa_proplist_sets(pl, PA_PROP_APPLICATION_ID, "org.PulseAudio.PulseAudio"); + pa_proplist_sets(pl, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION); + pa_tagstruct_put_proplist(reply, pl); + pa_proplist_free(pl); + } else + 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 + + if (u->version < 13) + /* Only for older PA versions we need to fill in the maxlength */ + u->maxlength = 4*1024*1024; + +#ifdef TUNNEL_SINK + u->tlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_TLENGTH_MSEC, &u->sink->sample_spec); + u->minreq = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MINREQ_MSEC, &u->sink->sample_spec); + u->prebuf = u->tlength; +#else + u->fragsize = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_FRAGSIZE_MSEC, &u->source->sample_spec); +#endif + +#ifdef TUNNEL_SINK pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_PLAYBACK_STREAM); pa_tagstruct_putu32(reply, tag = u->ctag++); - pa_tagstruct_puts(reply, name); + + if (u->version < 13) + 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, DEFAULT_MAXLENGTH); - pa_tagstruct_put_boolean(reply, 0); - pa_tagstruct_putu32(reply, DEFAULT_TLENGTH); - pa_tagstruct_putu32(reply, DEFAULT_PREBUF); - pa_tagstruct_putu32(reply, DEFAULT_MINREQ); + pa_tagstruct_putu32(reply, u->maxlength); + pa_tagstruct_put_boolean(reply, !PA_SINK_IS_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); + + if (u->version < 13) + 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, DEFAULT_MAXLENGTH); - pa_tagstruct_put_boolean(reply, 0); - pa_tagstruct_putu32(reply, DEFAULT_FRAGSIZE); + pa_tagstruct_putu32(reply, u->maxlength); + pa_tagstruct_put_boolean(reply, !PA_SOURCE_IS_OPENED(pa_source_get_state(u->source))); + pa_tagstruct_putu32(reply, u->fragsize); +#endif + + if (u->version >= 12) { + 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, TRUE); /* no_move */ + pa_tagstruct_put_boolean(reply, FALSE); /* variable_rate */ + } + + if (u->version >= 13) { + pa_proplist *pl; + + pa_tagstruct_put_boolean(reply, FALSE); /* start muted/peak detect*/ + pa_tagstruct_put_boolean(reply, TRUE); /* adjust_latency */ + + pl = pa_proplist_new(); + pa_proplist_sets(pl, PA_PROP_MEDIA_NAME, name); + pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "abstract"); + pa_tagstruct_put_proplist(reply, pl); + pa_proplist_free(pl); + +#ifndef TUNNEL_SINK + pa_tagstruct_putu32(reply, PA_INVALID_INDEX); /* direct on input */ #endif - + } + 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); } +/* Called from main context */ static void pstream_die_callback(pa_pstream *p, void *userdata) { struct userdata *u = userdata; - assert(p && u); - pa_log("stream died."); - die(u); + pa_assert(p); + pa_assert(u); + + pa_log_warn("Stream died."); + pa_module_unload_request(u->module); } +/* Called from main context */ static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) { struct userdata *u = userdata; - assert(p && packet && u); + + pa_assert(p); + pa_assert(packet); + pa_assert(u); if (pa_pdispatch_run(u->pdispatch, packet, creds, u) < 0) { - pa_log("invalid packet"); - die(u); + 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, PA_GCC_UNUSED int64_t offset, PA_GCC_UNUSED pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { +/* Called from main context */ +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; - assert(p && chunk && u); + + pa_assert(p); + pa_assert(chunk); + pa_assert(u); if (channel != u->channel) { - pa_log("recieved memory block on bad channel."); - die(u); + pa_log("Recieved memory block on bad channel."); + pa_module_unload_request(u->module); return; } - - pa_source_post(u->source, chunk); + + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, PA_UINT_TO_PTR(seek), offset, chunk); + + u->counter_delta += chunk->length; } + #endif +/* Called from main context */ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { struct userdata *u = userdata; pa_tagstruct *t; uint32_t tag; - assert(sc && u && u->client == sc); + + 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."); + pa_log("Connection failed: %s", pa_cstrerror(errno)); pa_module_unload_request(u->module); return; } @@ -701,207 +1577,130 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata #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)); - pa_pstream_send_tagstruct(u->pstream, t); - pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u, NULL); - -} - -#ifdef TUNNEL_SINK -static void sink_notify(pa_sink*sink) { - struct userdata *u; - assert(sink && sink->userdata); - u = sink->userdata; - - send_bytes(u); -} - -static pa_usec_t sink_get_latency(pa_sink *sink) { - struct userdata *u; - uint32_t l; - pa_usec_t usec = 0; - assert(sink && sink->userdata); - u = sink->userdata; - l = DEFAULT_TLENGTH; +#ifdef HAVE_CREDS +{ + pa_creds ucred; - if (l > u->requested_bytes) { - l -= u->requested_bytes; - usec += pa_bytes_to_usec(l, &u->sink->sample_spec); - } + if (pa_iochannel_creds_supported(io)) + pa_iochannel_creds_enable(io); - usec += u->host_latency; + ucred.uid = getuid(); + ucred.gid = getgid(); - return usec; + pa_pstream_send_tagstruct_with_creds(u->pstream, t, &ucred); } - -static int sink_get_hw_volume(pa_sink *sink) { - struct userdata *u; - assert(sink && sink->userdata); - u = sink->userdata; - - return 0; -} - -static int sink_set_hw_volume(pa_sink *sink) { - struct userdata *u; - pa_tagstruct *t; - uint32_t tag; - assert(sink && sink->userdata); - u = sink->userdata; - - t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_VOLUME); - pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->sink_name); - pa_tagstruct_put_cvolume(t, &sink->hw_volume); +#else pa_pstream_send_tagstruct(u->pstream, t); +#endif - return 0; -} - -static int sink_get_hw_mute(pa_sink *sink) { - struct userdata *u; - assert(sink && sink->userdata); - u = sink->userdata; + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u, NULL); - return 0; + pa_log_debug("Connection established, authenticating ..."); } -static int sink_set_hw_mute(pa_sink *sink) { +#ifdef TUNNEL_SINK + +/* Called from main context */ +static int sink_set_volume(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; uint32_t tag; - assert(sink && sink->userdata); + + pa_assert(sink); u = sink->userdata; + pa_assert(u); t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_MUTE); + pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME); pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->sink_name); - pa_tagstruct_put_boolean(t, !!sink->hw_muted); + pa_tagstruct_putu32(t, u->device_index); + pa_tagstruct_put_cvolume(t, &sink->volume); pa_pstream_send_tagstruct(u->pstream, t); return 0; } -#else -static pa_usec_t source_get_latency(pa_source *source) { - struct userdata *u; - assert(source && source->userdata); - u = source->userdata; - - return u->host_latency; -} - -static int source_get_hw_volume(pa_source *source) { - struct userdata *u; - assert(source && source->userdata); - u = source->userdata; - - return 0; -} - -static int source_set_hw_volume(pa_source *source) { +/* Called from main context */ +static int sink_set_mute(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; uint32_t tag; - assert(source && source->userdata); - u = source->userdata; - - t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SOURCE_VOLUME); - pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->source_name); - pa_tagstruct_put_cvolume(t, &source->hw_volume); - pa_pstream_send_tagstruct(u->pstream, t); - - return 0; -} -static int source_get_hw_mute(pa_source *source) { - struct userdata *u; - assert(source && source->userdata); - u = source->userdata; - - return 0; -} + pa_assert(sink); + u = sink->userdata; + pa_assert(u); -static int source_set_hw_mute(pa_source *source) { - struct userdata *u; - pa_tagstruct *t; - uint32_t tag; - assert(source && source->userdata); - u = source->userdata; + if (u->version < 11) + return -1; t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SOURCE_MUTE); + pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE); pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->source_name); - pa_tagstruct_put_boolean(t, !!source->hw_muted); + pa_tagstruct_putu32(t, u->device_index); + pa_tagstruct_put_boolean(t, !!sink->muted); pa_pstream_send_tagstruct(u->pstream, t); return 0; } + #endif +/* Called from main context */ static int load_key(struct userdata *u, const char*fn) { - assert(u); + pa_assert(u); + + u->auth_cookie_in_property = FALSE; - 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_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."); - + 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; + u->auth_cookie_in_property = TRUE; return 0; } -int pa__init(pa_core *c, pa_module*m) { +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; - - assert(c && m); + char *dn = NULL; +#ifdef TUNNEL_SINK + pa_sink_new_data data; +#else + pa_source_new_data data; +#endif + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - u = pa_xmalloc(sizeof(struct userdata)); - m->userdata = u; + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; u->module = m; - u->core = c; u->client = NULL; u->pdispatch = NULL; u->pstream = NULL; @@ -914,33 +1713,38 @@ int pa__init(pa_core *c, pa_module*m) { 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, 10); u->ctag = 1; u->device_index = u->channel = PA_INVALID_INDEX; - u->host_latency = 0; - u->auth_cookie_in_property = 0; + u->auth_cookie_in_property = FALSE; u->time_event = NULL; - + u->ignore_latency_before = 0; + u->transport_usec = 0; + u->transport_usec_valid = FALSE; + u->remote_suspended = u->remote_corked = FALSE; + u->counter = u->counter_delta = 0; + + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + 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."); + pa_log("No server specified."); goto fail; } - ss = c->default_sample_spec; + 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"); + pa_log("Invalid sample format specification"); goto fail; } - if (!(u->client = pa_socket_client_new_string(c->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) { - pa_log("failed to connect to server '%s'", u->server_name); + 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; } - - if (!u->client) - goto fail; pa_socket_client_set_callback(u->client, on_connection, u); @@ -949,76 +1753,167 @@ int pa__init(pa_core *c, pa_module*m) { 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(c, __FILE__, dn, 0, &ss, &map))) { - pa_log("failed to create sink."); + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + data.namereg_fail = TRUE; + pa_sink_new_data_set_name(&data, dn); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "%s%s%s", pa_strempty(u->sink_name), u->sink_name ? " on " : "", u->server_name); + pa_proplist_sets(data.proplist, "tunnel.remote.server", u->server_name); + if (u->sink_name) + pa_proplist_sets(data.proplist, "tunnel.remote.sink", u->sink_name); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); goto fail; } - u->sink->get_latency = sink_get_latency; - u->sink->get_hw_volume = sink_get_hw_volume; - u->sink->set_hw_volume = sink_set_hw_volume; - u->sink->get_hw_mute = sink_get_hw_mute; - u->sink->set_hw_mute = sink_set_hw_mute; - u->sink->notify = sink_notify; + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Tunnel to %s%s%s", u->sink_name ? u->sink_name : "", u->sink_name ? " on " : "", u->server_name)); - pa_xfree(t); + u->sink->set_state = sink_set_state; + u->sink->set_volume = sink_set_volume; + u->sink->set_mute = sink_set_mute; + + u->sink->refresh_volume = u->sink->refresh_muted = FALSE; + + pa_sink_set_latency_range(u->sink, MIN_NETWORK_LATENCY_USEC, 0); + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_owner(u->sink, m); #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(c, __FILE__, dn, 0, &ss, &map))) { - pa_log("failed to create source."); + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + data.namereg_fail = TRUE; + pa_source_new_data_set_name(&data, dn); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "%s%s%s", pa_strempty(u->source_name), u->source_name ? " on " : "", u->server_name); + pa_proplist_sets(data.proplist, "tunnel.remote.server", u->server_name); + if (u->source_name) + pa_proplist_sets(data.proplist, "tunnel.remote.source", u->source_name); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_NETWORK|PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log("Failed to create source."); goto fail; } - u->source->get_latency = source_get_latency; - u->source->get_hw_volume = source_get_hw_volume; - u->source->set_hw_volume = source_set_hw_volume; - u->source->get_hw_mute = source_get_hw_mute; - u->source->set_hw_mute = source_set_hw_mute; + u->source->parent.process_msg = source_process_msg; + u->source->set_state = source_set_state; u->source->userdata = u; - pa_source_set_description(u->source, t = pa_sprintf_malloc("Tunnel to %s%s%s", u->source_name ? u->source_name : "", u->source_name ? " on " : "", u->server_name)); - pa_xfree(t); + pa_source_set_latency_range(u->source, MIN_NETWORK_LATENCY_USEC, 0); - pa_source_set_owner(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); #endif - + pa_xfree(dn); u->time_event = NULL; + u->maxlength = 0; +#ifdef TUNNEL_SINK + u->tlength = u->minreq = u->prebuf = 0; +#else + u->fragsize = 0; +#endif + + 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(c, m); + pa__done(m); if (ma) pa_modargs_free(ma); pa_xfree(dn); - + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata* u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - close_stuff(u); +#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(c, PA_NATIVE_COOKIE_PROPERTY_NAME); - + 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 @@ -1026,7 +1921,9 @@ void pa__done(pa_core *c, pa_module*m) { #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 index efa59f40..d862c203 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -1,18 +1,18 @@ -/* $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 @@ -24,7 +24,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <sys/types.h> @@ -33,6 +32,8 @@ #include <ctype.h> #include <pulse/xmalloc.h> +#include <pulse/volume.h> +#include <pulse/timeval.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> @@ -42,57 +43,66 @@ #include <pulsecore/core-subscribe.h> #include <pulsecore/sink-input.h> #include <pulsecore/source-output.h> -#include <pulsecore/core-util.h> #include <pulsecore/namereg.h> -#include <pulse/volume.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_USAGE("table=<filename>") -PA_MODULE_VERSION(PACKAGE_VERSION) +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; - int volume_is_set; + pa_bool_t volume_is_set; pa_cvolume volume; - char *sink; - char *source; + char *sink, *source; }; struct userdata { + pa_core *core; pa_hashmap *hashmap; pa_subscription *subscription; - pa_hook_slot *sink_input_hook_slot, *source_output_hook_slot; - int modified; + 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; - - assert(s); - assert(v); + + pa_assert(s); + pa_assert(v); if (!isdigit(*s)) return NULL; k = strtol(s, &p, 0); - if (k <= 0 || k > PA_CHANNELS_MAX) + if (k <= 0 || k > (long) PA_CHANNELS_MAX) return NULL; - + v->channels = (unsigned) k; for (i = 0; i < v->channels; i++) { @@ -103,9 +113,9 @@ static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { k = strtol(p, &p, 0); - if (k < PA_VOLUME_MUTED) + if (k < (long) PA_VOLUME_MUTED) return NULL; - + v->values[i] = (pa_volume_t) k; } @@ -122,32 +132,28 @@ static int load_rules(struct userdata *u) { 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 (!(f = fopen(u->table_file, "r"))) { if (errno == ENOENT) { - pa_log_info("starting with empty ruleset."); + pa_log_info("Starting with empty ruleset."); ret = 0; } else - pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); - + 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; - int v_is_set; - + pa_bool_t v_is_set; + if (!fgets(ln, sizeof(buf_name), f)) break; n++; - + pa_strip_nl(ln); if (ln[0] == '#') @@ -168,7 +174,7 @@ static int load_rules(struct userdata *u) { continue; } - assert(ln == buf_source); + pa_assert(ln == buf_source); if (buf_volume[0]) { if (!parse_volume(buf_volume, &v)) { @@ -176,17 +182,17 @@ static int load_rules(struct userdata *u) { goto finish; } - v_is_set = 1; + v_is_set = TRUE; } else - v_is_set = 0; + 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)) @@ -203,7 +209,7 @@ static int load_rules(struct userdata *u) { } ret = 0; - + finish: if (f) { pa_lock_fd(fileno(f), 0); @@ -218,13 +224,14 @@ static int save_rules(struct userdata *u) { int ret = -1; void *state = NULL; struct rule *rule; - - 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)); + if (!u->modified) + return 0; + + pa_log_info("Saving rules..."); + + if (!(f = fopen(u->table_file, "w"))) { + pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); goto finish; } @@ -232,7 +239,7 @@ static int save_rules(struct userdata *u) { while ((rule = pa_hashmap_iterate(u->hashmap, &state, NULL))) { unsigned i; - + fprintf(f, "%s\n", rule->name); if (rule->volume_is_set) { @@ -241,14 +248,16 @@ static int save_rules(struct userdata *u) { 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); @@ -260,11 +269,11 @@ finish: static char* client_name(pa_client *c) { char *t, *e; - - if (!c->name || !c->driver) + + if (!pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME) || !c->driver) return NULL; - t = pa_sprintf_malloc("%s$%s", c->driver, c->name); + t = pa_sprintf_malloc("%s$%s", c->driver, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)); t[strcspn(t, "\n\r#")] = 0; if (!*t) { @@ -280,23 +289,38 @@ static char* client_name(pa_client *c) { * 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; - - assert(c); - assert(u); + + 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) && @@ -307,15 +331,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 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 { - assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT); + 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; } @@ -328,27 +352,27 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 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 = 1; - u->modified = 1; + 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 = 1; + u->modified = TRUE; } } else { - assert(so); + 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 = 1; + u->modified = TRUE; } } - + } else { pa_log_info("Creating new entry for <%s>", name); @@ -357,26 +381,60 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (si) { r->volume = *pa_sink_input_get_volume(si); - r->volume_is_set = 1; + r->volume_is_set = TRUE; r->sink = pa_xstrdup(si->sink->name); r->source = NULL; } else { - assert(so); - r->volume_is_set = 0; + 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 = 1; + 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_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { +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; - assert(data); + 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; @@ -387,21 +445,18 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d pa_log_info("Restoring volume for <%s>", r->name); pa_sink_input_new_data_set_volume(data, &r->volume); } - - 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 source_output_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) { +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; - assert(data); + pa_assert(data); if (!data->client || !(name = client_name(data->client))) return PA_HOOK_OK; @@ -416,12 +471,12 @@ static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output return PA_HOOK_OK; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - - assert(c); - assert(m); + 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"); @@ -429,35 +484,56 @@ int pa__init(pa_core *c, pa_module*m) { } 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->modified = FALSE; u->subscription = NULL; - u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL)); - u->modified = 0; - + 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 (!(u->table_file = pa_state_path(pa_modargs_get_value(ma, "table", DEFAULT_VOLUME_TABLE_FILE)))) + goto fail; + + 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(c, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); - u->sink_input_hook_slot = pa_hook_connect(&c->hook_sink_input_new, (pa_hook_cb_t) sink_input_hook_callback, u); - u->source_output_hook_slot = pa_hook_connect(&c->hook_source_output_new, (pa_hook_cb_t) source_output_hook_callback, u); + 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_EARLY, (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_EARLY, (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_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); pa_modargs_free(ma); return 0; fail: - pa__done(c, m); - + pa__done(m); if (ma) pa_modargs_free(ma); - + return -1; } static void free_func(void *p, void *userdata) { struct rule *r = p; - assert(r); + pa_assert(r); pa_xfree(r->name); pa_xfree(r->sink); @@ -465,11 +541,10 @@ static void free_func(void *p, void *userdata) { pa_xfree(r); } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata* u; - - assert(c); - assert(m); + + pa_assert(m); if (!(u = m->userdata)) return; @@ -477,21 +552,21 @@ void pa__done(pa_core *c, pa_module*m) { if (u->subscription) pa_subscription_free(u->subscription); - if (u->sink_input_hook_slot) - pa_hook_slot_free(u->sink_input_hook_slot); - if (u->source_output_hook_slot) - pa_hook_slot_free(u->source_output_hook_slot); + 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) { - - if (u->modified) - save_rules(u); - + 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 index 4043c136..b452c3bf 100644 --- a/src/modules/module-waveout.c +++ b/src/modules/module-waveout.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,7 +26,6 @@ #include <windows.h> #include <mmsystem.h> -#include <assert.h> #include <pulse/mainloop-api.h> @@ -47,7 +47,8 @@ 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>" + "source_name=<name for the source> " + "device=<device number> " "record=<enable source?> " "playback=<enable sink?> " "format=<sample format> " @@ -90,6 +91,7 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", "source_name", + "device", "record", "playback", "fragments", @@ -172,7 +174,7 @@ static void do_write(struct userdata *u) pa_log_error(__FILE__ ": ERROR: Unable to write waveOut block: %d", res); } - + u->written_bytes += hdr->dwBufferLength; EnterCriticalSection(&u->crit); @@ -233,7 +235,7 @@ static void do_read(struct userdata *u) pa_log_error(__FILE__ ": ERROR: Unable to add waveIn block: %d", res); } - + free_frags--; u->cur_ihdr++; u->cur_ihdr %= u->fragments; @@ -432,6 +434,7 @@ int pa__init(pa_core *c, pa_module*m) { 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; @@ -455,6 +458,12 @@ int pa__init(pa_core *c, pa_module*m) { 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) { @@ -474,7 +483,7 @@ int pa__init(pa_core *c, pa_module*m) { u = pa_xmalloc(sizeof(struct userdata)); if (record) { - if (waveInOpen(&hwi, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { + 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; } @@ -486,7 +495,7 @@ int pa__init(pa_core *c, pa_module*m) { } if (playback) { - if (waveOutOpen(&hwo, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { + 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; } @@ -561,7 +570,7 @@ int pa__init(pa_core *c, pa_module*m) { u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size); assert(u->ohdrs); } - + u->module = m; m->userdata = u; @@ -585,7 +594,7 @@ fail: if (ma) pa_modargs_free(ma); - + return -1; } @@ -597,7 +606,7 @@ void pa__done(pa_core *c, pa_module*m) { if (!(u = m->userdata)) return; - + if (u->event) c->mainloop->time_free(u->event); @@ -608,12 +617,12 @@ void pa__done(pa_core *c, pa_module*m) { 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); @@ -633,6 +642,6 @@ void pa__done(pa_core *c, pa_module*m) { 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 index 48e95c8c..f7be48f7 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> @@ -43,20 +42,11 @@ #include "module-x11-bell-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("X11 Bell interceptor") -PA_MODULE_VERSION(PACKAGE_VERSION) -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; -}; +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>"); static const char* const valid_modargs[] = { "sink", @@ -65,30 +55,34 @@ static const char* const valid_modargs[] = { NULL }; -static int ring_bell(struct userdata *u, int percent) { - pa_sink *s; - assert(u); +struct userdata { + pa_core *core; + pa_module *module; - if (!(s = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) { - pa_log("Invalid sink: %s", u->sink_name); - return -1; - } + int xkb_event_base; - pa_scache_play_item(u->core, u->scache_item, s, (percent*PA_VOLUME_NORM)/100); - return 0; -} + char *sink_name; + char *scache_item; + + pa_x11_wrapper *x11_wrapper; + pa_x11_client *x11_client; +}; -static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { +static int x11_event_cb(pa_x11_wrapper *w, XEvent *e, void *userdata) { XkbBellNotifyEvent *bne; struct userdata *u = userdata; - assert(w && e && u && u->x11_wrapper == w); + + 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 (ring_bell(u, bne->percent) < 0) { + if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, TRUE, (bne->percent*PA_VOLUME_NORM)/100, NULL, NULL) < 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); } @@ -96,30 +90,52 @@ static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { return 1; } -int pa__init(pa_core *c, pa_module*m) { +static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) { + struct userdata *u = userdata; + + pa_assert(w); + pa_assert(u); + pa_assert(u->x11_wrapper == w); + + if (u->x11_client) + pa_x11_client_free(u->x11_client); + + if (u->x11_wrapper) + pa_x11_wrapper_unref(u->x11_wrapper); + + u->x11_client = NULL; + u->x11_wrapper = NULL; + + pa_module_unload_request(u->module); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; pa_modargs *ma = NULL; int major, minor; unsigned int auto_ctrls, auto_values; - assert(c && m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - - m->userdata = u = pa_xmalloc(sizeof(struct userdata)); - u->core = c; - u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell")); + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "bell-window-system")); u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->x11_client = NULL; - if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", 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; @@ -128,7 +144,6 @@ int pa__init(pa_core *c, pa_module*m) { 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; @@ -139,23 +154,28 @@ int pa__init(pa_core *c, pa_module*m) { 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); - + u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_cb, x11_kill_cb, u); + pa_modargs_free(ma); - + return 0; - + fail: if (ma) pa_modargs_free(ma); - if (m->userdata) - pa__done(c, m); + + pa__done(m); + return -1; } -void pa__done(pa_core *c, pa_module*m) { - struct userdata *u = m->userdata; - assert(c && m && u); +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; pa_xfree(u->scache_item); pa_xfree(u->sink_name); diff --git a/src/modules/module-x11-publish.c b/src/modules/module-x11-publish.c index f2cace14..705d90f4 100644 --- a/src/modules/module-x11-publish.c +++ b/src/modules/module-x11-publish.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -52,10 +51,11 @@ #include "module-x11-publish-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("X11 Credential Publisher") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("display=<X11 display>") +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", @@ -67,39 +67,62 @@ static const char* const valid_modargs[] = { struct userdata { pa_core *core; - pa_x11_wrapper *x11_wrapper; + pa_module *module; + char *id; uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; - int auth_cookie_in_property; + pa_bool_t auth_cookie_in_property; + + pa_x11_wrapper *x11_wrapper; + pa_x11_client *x11_client; }; +static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) { + struct userdata *u = userdata; + + pa_assert(w); + pa_assert(u); + pa_assert(u->x11_wrapper == w); + + if (u->x11_client) + pa_x11_client_free(u->x11_client); + + if (u->x11_wrapper) + pa_x11_wrapper_unref(u->x11_wrapper); + + u->x11_client = NULL; + u->x11_wrapper = NULL; + + pa_module_unload_request(u->module); +} + static int load_key(struct userdata *u, const char*fn) { - assert(u); + pa_assert(u); + + u->auth_cookie_in_property = FALSE; - 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."); - + 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; + u->auth_cookie_in_property = TRUE; return 0; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; char hn[256], un[128]; @@ -108,32 +131,40 @@ int pa__init(pa_core *c, pa_module*m) { 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 = c; + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; u->id = NULL; - u->auth_cookie_in_property = 0; + u->auth_cookie_in_property = FALSE; + u->x11_client = NULL; + u->x11_wrapper = NULL; if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0) goto fail; - if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL)))) + if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) goto fail; - if (!(l = pa_property_get(c, PA_NATIVE_SERVER_PROPERTY_NAME))) + 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); @@ -144,25 +175,33 @@ int pa__init(pa_core *c, pa_module*m) { 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))); - + + u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, u); + pa_modargs_free(ma); + return 0; - + fail: if (ma) pa_modargs_free(ma); - pa__done(c, m); + pa__done(m); + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata*u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - + + if (u->x11_client) + pa_x11_client_free(u->x11_client); + if (u->x11_wrapper) { char t[256]; @@ -177,15 +216,13 @@ void pa__done(pa_core *c, pa_module*m) { 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(c, PA_NATIVE_COOKIE_PROPERTY_NAME); + 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..696826d8 --- /dev/null +++ b/src/modules/module-x11-xsmp.c @@ -0,0 +1,247 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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 <pulsecore/x11wrap.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); +PA_MODULE_USAGE("session_manager=<session manager string> display=<X11 display>"); + +static pa_bool_t ice_in_use = FALSE; + +static const char* const valid_modargs[] = { + "session_manager", + "display", + NULL +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_client *client; + SmcConn connection; + pa_x11_wrapper *x11; +}; + +static void die_cb(SmcConn connection, SmPointer client_data){ + struct userdata *u = client_data; + pa_assert(u); + + pa_log_debug("Got die message from XSMP."); + + pa_x11_wrapper_kill(u->x11); + + pa_x11_wrapper_unref(u->x11); + u->x11 = NULL; + + pa_module_unload_request(u->module); +} + +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) { + struct pa_core *c = client_data; + + 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, *k; + SmcCallbacks callbacks; + SmProp prop_program, prop_user; + SmProp *prop_list[2]; + SmPropValue val_program, val_user; + struct userdata *u; + const char *e; + + 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 = TRUE; + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + u->client = NULL; + u->connection = NULL; + u->x11 = NULL; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (!(u->x11 = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) + goto fail; + + e = pa_modargs_get_value(ma, "session_manager", NULL); + + if (!e && !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 = u; + 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 (!(u->connection = SmcOpenConnection( + (char*) e, 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(u->connection, PA_ELEMENTSOF(prop_list), prop_list); + + pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(u->connection), client_id); + k = pa_sprintf_malloc("XSMP Session on %s as %s", vendor, client_id); + u->client = pa_client_new(u->core, __FILE__, k); + pa_xfree(k); + + pa_proplist_sets(u->client->proplist, "xsmp.vendor", vendor); + pa_proplist_sets(u->client->proplist, "xsmp.client.id", 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) { + struct userdata *u; + + pa_assert(m); + + if ((u = m->userdata)) { + + if (u->connection) + SmcCloseConnection(u->connection, 0, NULL); + + if (u->client) + pa_client_free(u->client); + + if (u->x11) + pa_x11_wrapper_unref(u->x11); + + pa_xfree(u); + } + + if (ice_in_use) { + IceRemoveConnectionWatch(new_ice_connection, m->core); + ice_in_use = FALSE; + } +} diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c new file mode 100644 index 00000000..2fc81370 --- /dev/null +++ b/src/modules/module-zeroconf-discover.c @@ -0,0 +1,438 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to 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_channel_map_init_extend(&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_channel_map_init_extend(&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 %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 index 696d8afe..cb9c1285 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -33,11 +32,11 @@ #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/autoload.h> #include <pulsecore/sink.h> #include <pulsecore/source.h> #include <pulsecore/native-common.h> @@ -51,74 +50,89 @@ #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_USAGE("port=<IP port number>") +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; - char *name; - enum { UNPUBLISHED, PUBLISHED_REAL, PUBLISHED_AUTOLOAD } published ; - - struct { - int valid; - pa_namereg_type_t type; - uint32_t index; - } loaded; - - struct { - int valid; - pa_namereg_type_t type; - uint32_t index; - } autoload; + pa_object *device; + enum service_subtype subtype; }; struct userdata { pa_core *core; + pa_module *module; AvahiPoll *avahi_poll; AvahiClient *client; + pa_hashmap *services; - pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray; - pa_subscription *subscription; 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 userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description) { - assert(u && s && s->loaded.valid && ret_ss && ret_description); +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); - if (s->loaded.type == PA_NAMEREG_SINK) { - pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index); - assert(sink); *ret_ss = sink->sample_spec; - *ret_description = sink->description; - } else if (s->loaded.type == PA_NAMEREG_SOURCE) { - pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index); - assert(source); + *ret_map = sink->channel_map; + *ret_name = sink->name; + *ret_description = pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_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_description = source->description; + *ret_map = source->channel_map; + *ret_name = source->name; + *ret_description = pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)); + *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL); + } else - assert(0); + pa_assert_not_reached(); } static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) { char s[128]; - assert(c); + + 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))); @@ -128,332 +142,276 @@ static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) { return l; } -static int publish_service(struct userdata *u, struct service *s); +static int publish_service(struct service *s); static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { struct service *s = userdata; - if (state == AVAHI_ENTRY_GROUP_COLLISION) { - char *t; + pa_assert(s); + + switch (state) { + + case AVAHI_ENTRY_GROUP_ESTABLISHED: + pa_log_info("Successfully established service %s.", s->service_name); + break; - t = avahi_alternative_service_name(s->service_name); - pa_xfree(s->service_name); - s->service_name = t; + case AVAHI_ENTRY_GROUP_COLLISION: { + char *t; - publish_service(s->userdata, s); + 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 int publish_service(struct userdata *u, struct service *s) { +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; - assert(u); - assert(s); + const char * const subtype_text[] = { + [SUBTYPE_HARDWARE] = "hardware", + [SUBTYPE_VIRTUAL] = "virtual", + [SUBTYPE_MONITOR] = "monitor" + }; - if (!u->client || avahi_client_get_state(u->client) != AVAHI_CLIENT_S_RUNNING) - return 0; - - if ((s->published == PUBLISHED_REAL && s->loaded.valid) || - (s->published == PUBLISHED_AUTOLOAD && s->autoload.valid && !s->loaded.valid)) + pa_assert(s); + + if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING) return 0; - if (s->published != UNPUBLISHED) { - avahi_entry_group_reset(s->entry_group); - s->published = UNPUBLISHED; - } - - if (s->loaded.valid || s->autoload.valid) { - pa_namereg_type_t type; - - if (!s->entry_group) { - if (!(s->entry_group = avahi_entry_group_new(u->client, service_entry_group_callback, s))) { - pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(u->client))); - goto finish; - } + 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; } - - txt = avahi_string_list_add_pair(txt, "device", s->name); - txt = txt_record_server_data(u->core, txt); - - if (s->loaded.valid) { - char *description; - pa_sample_spec ss; - - get_service_data(u, s, &ss, &description); - - 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)); - if (description) - txt = avahi_string_list_add_pair(txt, "description", description); - - type = s->loaded.type; - } else if (s->autoload.valid) - type = s->autoload.type; - - if (avahi_entry_group_add_service_strlst( + } 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, - type == PA_NAMEREG_SINK ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, - NULL, + SERVICE_TYPE_SOURCE, NULL, - u->port, - txt) < 0) { - - pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(u->client))); - goto finish; - } - - if (avahi_entry_group_commit(s->entry_group) < 0) { - pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(u->client))); + 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 (s->loaded.valid) - s->published = PUBLISHED_REAL; - else if (s->autoload.valid) - s->published = PUBLISHED_AUTOLOAD; } - + + 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: - if (s->published == UNPUBLISHED) { - /* Remove this service */ + /* Remove this service */ + if (r < 0) + service_free(s); - if (s->entry_group) - avahi_entry_group_free(s->entry_group); - - pa_hashmap_remove(u->services, s->name); - pa_xfree(s->name); - pa_xfree(s->service_name); - pa_xfree(s); - } + avahi_string_list_free(txt); - if (txt) - avahi_string_list_free(txt); - return r; } -static struct service *get_service(struct userdata *u, const char *name, const char *description) { +static struct service *get_service(struct userdata *u, pa_object *device) { struct service *s; - char hn[64]; - - if ((s = pa_hashmap_get(u->services, name))) + 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->published = UNPUBLISHED; - s->name = pa_xstrdup(name); - s->loaded.valid = s->autoload.valid = 0; - s->service_name = pa_sprintf_malloc("%s on %s", description ? description : s->name, pa_get_host_name(hn, sizeof(hn))); + s->device = device; + + if (pa_sink_isinstance(device)) { + if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) + n = PA_SINK(device)->name; + } else { + if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) + n = PA_SOURCE(device)->name; + } - pa_hashmap_put(u->services, s->name, s); + 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 int publish_sink(struct userdata *u, pa_sink *s) { - struct service *svc; - int ret; - assert(u && s); - - svc = get_service(u, s->name, s->description); - if (svc->loaded.valid) - return publish_service(u, svc); +static void service_free(struct service *s) { + pa_assert(s); - svc->loaded.valid = 1; - svc->loaded.type = PA_NAMEREG_SINK; - svc->loaded.index = s->index; + pa_hashmap_remove(s->userdata->services, s->device); - if ((ret = publish_service(u, svc)) < 0) - return ret; + if (s->entry_group) { + pa_log_debug("Removing entry group for %s.", s->service_name); + avahi_entry_group_free(s->entry_group); + } - pa_dynarray_put(u->sink_dynarray, s->index, svc); - return ret; + pa_xfree(s->service_name); + pa_xfree(s); } -static int publish_source(struct userdata *u, pa_source *s) { - struct service *svc; - int ret; - - assert(u && s); - - svc = get_service(u, s->name, s->description); - if (svc->loaded.valid) - return publish_service(u, svc); +static pa_bool_t shall_ignore(pa_object *o) { + pa_object_assert_ref(o); - svc->loaded.valid = 1; - svc->loaded.type = PA_NAMEREG_SOURCE; - svc->loaded.index = s->index; + if (pa_sink_isinstance(o)) + return !!(PA_SINK(o)->flags & PA_SINK_NETWORK); - pa_dynarray_put(u->source_dynarray, s->index, svc); - - if ((ret = publish_service(u, svc)) < 0) - return ret; + if (pa_source_isinstance(o)) + return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK); - pa_dynarray_put(u->sink_dynarray, s->index, svc); - return ret; + pa_assert_not_reached(); } -static int publish_autoload(struct userdata *u, pa_autoload_entry *s) { - struct service *svc; - int ret; - - assert(u && s); - - svc = get_service(u, s->name, NULL); - if (svc->autoload.valid) - return publish_service(u, svc); - - svc->autoload.valid = 1; - svc->autoload.type = s->type; - svc->autoload.index = s->index; - - if ((ret = publish_service(u, svc)) < 0) - return ret; - - pa_dynarray_put(u->autoload_dynarray, s->index, svc); - return ret; -} - -static int remove_sink(struct userdata *u, uint32_t idx) { - struct service *svc; - assert(u && idx != PA_INVALID_INDEX); +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 (!(svc = pa_dynarray_get(u->sink_dynarray, idx))) - return 0; + if (!shall_ignore(o)) + publish_service(get_service(u, o)); - if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK) - return 0; - - svc->loaded.valid = 0; - pa_dynarray_put(u->sink_dynarray, idx, NULL); - - return publish_service(u, svc); + return PA_HOOK_OK; } -static int remove_source(struct userdata *u, uint32_t idx) { - struct service *svc; - assert(u && idx != PA_INVALID_INDEX); - - if (!(svc = pa_dynarray_get(u->source_dynarray, idx))) - return 0; +static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct service *s; - if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE) - return 0; + pa_assert(c); + pa_object_assert_ref(o); - svc->loaded.valid = 0; - pa_dynarray_put(u->source_dynarray, idx, NULL); + if ((s = pa_hashmap_get(u->services, o))) + service_free(s); - return publish_service(u, svc); + return PA_HOOK_OK; } -static int remove_autoload(struct userdata *u, uint32_t idx) { - struct service *svc; - assert(u && idx != PA_INVALID_INDEX); - - if (!(svc = pa_dynarray_get(u->autoload_dynarray, idx))) - return 0; - - if (!svc->autoload.valid) - return 0; - - svc->autoload.valid = 0; - pa_dynarray_put(u->autoload_dynarray, idx, NULL); - - return publish_service(u, svc); -} +static int publish_main_service(struct userdata *u); -static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { +static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { struct userdata *u = userdata; - assert(u && c); + pa_assert(u); - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) - case PA_SUBSCRIPTION_EVENT_SINK: { - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - pa_sink *sink; + switch (state) { - if ((sink = pa_idxset_get_by_index(c->sinks, idx))) { - if (publish_sink(u, sink) < 0) - goto fail; - } - } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - if (remove_sink(u, idx) < 0) - goto fail; - } - + case AVAHI_ENTRY_GROUP_ESTABLISHED: + pa_log_info("Successfully established main service."); break; - case PA_SUBSCRIPTION_EVENT_SOURCE: + case AVAHI_ENTRY_GROUP_COLLISION: { + char *t; - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - pa_source *source; - - if ((source = pa_idxset_get_by_index(c->sources, idx))) { - if (publish_source(u, source) < 0) - goto fail; - } - } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - if (remove_source(u, idx) < 0) - goto fail; - } - - break; + 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; - case PA_SUBSCRIPTION_EVENT_AUTOLOAD: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - pa_autoload_entry *autoload; - - if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, idx))) { - if (publish_autoload(u, autoload) < 0) - goto fail; - } - } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - if (remove_autoload(u, idx) < 0) - goto fail; - } - + publish_main_service(u); break; - } - - return; - -fail: - if (u->subscription) { - pa_subscription_free(u->subscription); - u->subscription = NULL; - } -} - -static int publish_main_service(struct userdata *u); - -static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { - struct userdata *u = userdata; - assert(u); + } - if (state == AVAHI_ENTRY_GROUP_COLLISION) { - char *t; + case AVAHI_ENTRY_GROUP_FAILURE: { + pa_log("Failed to register main service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); - t = avahi_alternative_service_name(u->service_name); - pa_xfree(u->service_name); - u->service_name = t; + avahi_entry_group_free(g); + u->main_entry_group = NULL; + break; + } - publish_main_service(u); + 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))); @@ -461,8 +419,8 @@ static int publish_main_service(struct userdata *u) { } } else avahi_entry_group_reset(u->main_entry_group); - - txt = txt_record_server_data(u->core, NULL); + + txt = txt_record_server_data(u->core, txt); if (avahi_entry_group_add_service_strlst( u->main_entry_group, @@ -474,18 +432,18 @@ static int publish_main_service(struct userdata *u) { 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); @@ -495,197 +453,196 @@ fail: static int publish_all_services(struct userdata *u) { pa_sink *sink; pa_source *source; - pa_autoload_entry *autoload; int r = -1; uint32_t idx; - - assert(u); - pa_log_debug("Publishing services in Zeroconf"); + pa_assert(u); - for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx)) - if (publish_sink(u, sink) < 0) - goto fail; + pa_log_debug("Publishing services in Zeroconf"); - for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx)) - if (publish_source(u, source) < 0) - goto fail; + 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))); - if (u->core->autoload_idxset) - for (autoload = pa_idxset_first(u->core->autoload_idxset, &idx); autoload; autoload = pa_idxset_next(u->core->autoload_idxset, &idx)) - if (publish_autoload(u, autoload) < 0) - goto fail; + 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, int rem) { +static void unpublish_all_services(struct userdata *u, pa_bool_t rem) { void *state = NULL; struct service *s; - - assert(u); + + 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 + } else { avahi_entry_group_reset(s->entry_group); + pa_log_debug("Resetting entry group for %s.", s->service_name); + } } - - s->published = UNPUBLISHED; } 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 + } 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; - assert(c); + + 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: - unpublish_all_services(u, 0); + 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; - unpublish_all_services(u, 1); + + 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("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + 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_core *c, pa_module*m) { +int pa__init(pa_module*m) { + struct userdata *u; uint32_t port = PA_NATIVE_DEFAULT_PORT; pa_modargs *ma = NULL; - char hn[256]; + char hn[256], un[256]; int error; if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments."); + 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."); + 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 = c; + u->core = m->core; + u->module = m; u->port = (uint16_t) port; - u->avahi_poll = pa_avahi_poll_new(c->mainloop); - - u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - u->sink_dynarray = pa_dynarray_new(); - u->source_dynarray = pa_dynarray_new(); - u->autoload_dynarray = pa_dynarray_new(); + 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->subscription = pa_subscription_new(c, - PA_SUBSCRIPTION_MASK_SINK| - PA_SUBSCRIPTION_MASK_SOURCE| - PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u); + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (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_LATE, (pa_hook_cb_t) device_unlink_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (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_LATE, (pa_hook_cb_t) device_unlink_cb, u); u->main_entry_group = NULL; - u->service_name = pa_xstrdup(pa_get_host_name(hn, sizeof(hn))); + 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("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); goto fail; } pa_modargs_free(ma); - + return 0; - + fail: - pa__done(c, m); + pa__done(m); if (ma) pa_modargs_free(ma); - - return -1; -} - -static void service_free(void *p, void *userdata) { - struct service *s = p; - struct userdata *u = userdata; - - assert(s); - assert(u); - if (s->entry_group) - avahi_entry_group_free(s->entry_group); - - pa_xfree(s->service_name); - pa_xfree(s->name); - pa_xfree(s); + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata*u; - assert(c && m); + pa_assert(m); if (!(u = m->userdata)) return; - if (u->services) - pa_hashmap_free(u->services, service_free, u); + if (u->services) { + struct service *s; - if (u->subscription) - pa_subscription_free(u->subscription); + while ((s = pa_hashmap_get_first(u->services))) + service_free(s); - if (u->sink_dynarray) - pa_dynarray_free(u->sink_dynarray, NULL, NULL); - if (u->source_dynarray) - pa_dynarray_free(u->source_dynarray, NULL, NULL); - if (u->autoload_dynarray) - pa_dynarray_free(u->autoload_dynarray, NULL, NULL); - + 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 index 0aaf6971..2791e165 100644 --- a/src/modules/oss-util.c +++ b/src/modules/oss-util.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <sys/soundcard.h> #include <sys/ioctl.h> #include <stdio.h> @@ -34,9 +34,11 @@ #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" @@ -44,63 +46,62 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) { int fd = -1; int caps; - assert(device && mode && (*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY)); + pa_assert(device); + pa_assert(mode); + pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY); if(!pcaps) pcaps = ∩︀ - + if (*mode == O_RDWR) { - if ((fd = open(device, O_RDWR|O_NDELAY)) >= 0) { - int dcaps, *tcaps; + if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) { ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); - tcaps = pcaps ? pcaps : &dcaps; - - if (ioctl(fd, SNDCTL_DSP_GETCAPS, tcaps) < 0) { + if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); goto fail; } - if (*tcaps & DSP_CAP_DUPLEX) + if (*pcaps & DSP_CAP_DUPLEX) goto success; pa_log_warn("'%s' doesn't support full duplex", device); - close(fd); + pa_close(fd); } - - if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY)) < 0) { - if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY)) < 0) { + + 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)) < 0) { + if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) { pa_log("open('%s'): %s", device, pa_cstrerror(errno)); goto fail; } - } - -success: + } *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 +#ifdef DSP_CAP_FREERATE *pcaps & DSP_CAP_FREERATE ? " FREERATE" : "", #else "", @@ -119,7 +120,7 @@ success: #ifdef DSP_CAP_MULTI *pcaps & DSP_CAP_MULTI ? " MULTI" : "", #else - "", + "", #endif #ifdef DSP_CAP_OUTPUT *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "", @@ -139,20 +140,20 @@ success: #endif *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : ""); - pa_fd_set_cloexec(fd, 1); - + pa_make_fd_cloexec(fd); + return fd; fail: if (fd >= 0) - close(fd); + 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, @@ -161,12 +162,15 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { [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 */ }; - assert(fd >= 0 && ss); + 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; @@ -190,13 +194,13 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { 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; } - assert(channels > 0); + pa_assert(channels > 0); if (ss->channels != channels) { pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels); @@ -208,7 +212,7 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno)); return -1; } - assert(speed > 0); + 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); @@ -229,14 +233,14 @@ static int simple_log2(int v) { 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; @@ -245,27 +249,29 @@ int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { return 0; } -static int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) { +int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume) { char cv[PA_CVOLUME_SNPRINT_MAX]; unsigned vol; - assert(fd >= 0); - assert(ss); - assert(volume); - + 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 = ss->channels) >= 2) + 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; } -static int pa_oss_set_volume(int fd, int mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { +int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { char cv[PA_CVOLUME_SNPRINT_MAX]; unsigned vol; pa_volume_t l, r; @@ -278,7 +284,7 @@ static int pa_oss_set_volume(int fd, int mixer, const pa_sample_spec *ss, const 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; @@ -286,42 +292,50 @@ static int pa_oss_set_volume(int fd, int mixer, const pa_sample_spec *ss, const return 0; } -int pa_oss_get_pcm_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume) { - return pa_oss_get_volume(fd, SOUND_MIXER_READ_PCM, ss, volume); -} +static int get_device_number(const char *dev) { + const char *p, *e; + char *rp = NULL; + int r; -int pa_oss_set_pcm_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume) { - return pa_oss_set_volume(fd, SOUND_MIXER_WRITE_PCM, ss, volume); -} + if (!(p = rp = pa_readlink(dev))) { + if (errno != EINVAL && errno != ENOLINK) { + r = -1; + goto finish; + } -int pa_oss_get_input_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume) { - return pa_oss_get_volume(fd, SOUND_MIXER_READ_IGAIN, ss, volume); -} + 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; -int pa_oss_set_input_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume) { - return pa_oss_set_volume(fd, SOUND_MIXER_WRITE_IGAIN, ss, volume); +finish: + pa_xfree(rp); + return r; } int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { FILE *f; - const char *e = NULL; int n, r = -1; int b = 0; - if (strncmp(dev, "/dev/dsp", 8) == 0) - e = dev+8; - else if (strncmp(dev, "/dev/adsp", 9) == 0) - e = dev+9; - else + if ((n = get_device_number(dev)) < 0) return -1; - if (*e == 0) - n = 0; - else if (*e >= '0' && *e <= '9' && *(e+1) == 0) - n = *e - '0'; - else - return -1; - if (!(f = fopen("/dev/sndstat", "r")) && !(f = fopen("/proc/sndstat", "r")) && !(f = fopen("/proc/asound/oss/sndstat", "r"))) { @@ -335,7 +349,7 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { while (!feof(f)) { char line[64]; int device; - + if (!fgets(line, sizeof(line), f)) break; @@ -348,19 +362,19 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { if (line[0] == 0) break; - + if (sscanf(line, "%i: ", &device) != 1) continue; if (device == n) { char *k = strchr(line, ':'); - assert(k); + 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; @@ -370,3 +384,34 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { 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 index 12855f4e..654f7bba 100644 --- a/src/modules/oss-util.h +++ b/src/modules/oss-util.h @@ -1,21 +1,22 @@ #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 @@ -30,12 +31,11 @@ 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_get_pcm_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume); -int pa_oss_set_pcm_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume); - -int pa_oss_get_input_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume); -int pa_oss_set_input_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume); +int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume); +int pa_oss_get_volume(int fd, unsigned long 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/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 338d57cf..cff5cf8b 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -1,17 +1,19 @@ /*** 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 @@ -22,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> @@ -30,6 +31,7 @@ #include <errno.h> #include <string.h> #include <unistd.h> +#include <poll.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> @@ -45,6 +47,11 @@ #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 <pulsecore/time-smoother.h> #include "module-rtp-recv-symdef.h" @@ -52,19 +59,22 @@ #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_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 MEMBLOCKQ_MAXLENGTH (1024*1024*40) #define MAX_SESSIONS 16 -#define DEATH_TIMEOUT 20000000 +#define DEATH_TIMEOUT 20 +#define RATE_UPDATE_INTERVAL (5*PA_USEC_PER_SEC) +#define LATENCY_USEC (500*PA_USEC_PER_MSEC) static const char* const valid_modargs[] = { "sink", @@ -74,102 +84,148 @@ static const char* const valid_modargs[] = { struct session { struct userdata *userdata; + PA_LLIST_FIELDS(struct session); pa_sink_input *sink_input; pa_memblockq *memblockq; - pa_time_event *death_event; - - int first_packet; + pa_bool_t first_packet; uint32_t ssrc; uint32_t offset; struct pa_sdp_info sdp_info; pa_rtp_context rtp_context; - pa_io_event* rtp_event; + + pa_rtpoll_item *rtpoll_item; + + pa_atomic_t timestamp; + + pa_smoother *smoother; + pa_usec_t intended_latency; + pa_usec_t sink_latency; + + pa_usec_t last_rate_update; }; struct userdata { pa_module *module; - pa_core *core; pa_sap_context sap_context; pa_io_event* sap_event; - pa_hashmap *by_origin; + 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, int from_hash); +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; -static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) { + 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_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { struct session *s; - assert(i); - s = i->userdata; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + if (pa_memblockq_peek(s->memblockq, chunk) < 0) + return -1; - return pa_memblockq_peek(s->memblockq, chunk); + pa_memblockq_drop(s->memblockq, chunk->length); + + return 0; } -static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { struct session *s; - assert(i); - s = i->userdata; - pa_memblockq_drop(s->memblockq, chunk, length); + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + pa_memblockq_rewind(s->memblockq, nbytes); } -static void sink_input_kill(pa_sink_input* i) { +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { struct session *s; - assert(i); - s = i->userdata; - session_free(s, 1); + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + pa_memblockq_set_maxrewind(s->memblockq, nbytes); } -static pa_usec_t sink_input_get_latency(pa_sink_input *i) { +/* Called from main context */ +static void sink_input_kill(pa_sink_input* i) { struct session *s; - assert(i); - s = i->userdata; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); - return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &i->sample_spec); + session_free(s); } -static void rtp_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { - struct session *s = userdata; +/* 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 tv; - - assert(m); - assert(e); - assert(s); - assert(fd == s->rtp_context.fd); - assert(flags == PA_IO_EVENT_INPUT); - - if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->core->mempool) < 0) - return; + 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; + return 0; } - + if (!s->first_packet) { - s->first_packet = 1; + s->first_packet = TRUE; s->ssrc = s->rtp_context.ssrc; s->offset = s->rtp_context.timestamp; - if (s->ssrc == s->userdata->core->cookie) - pa_log_warn("WARNING! Detected RTP packet loop!"); + 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; + return 0; } } @@ -181,40 +237,125 @@ static void rtp_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event delta = k; else delta = j; - + pa_memblockq_seek(s->memblockq, delta * s->rtp_context.frame_size, PA_SEEK_RELATIVE); + pa_rtclock_get(&now); + + pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec(pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); + 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); + pa_log_warn("Queue overrun"); + pa_memblockq_seek(s->memblockq, chunk.length, PA_SEEK_RELATIVE); } - + + pa_log("blocks in q: %u", pa_memblockq_get_nblocks(s->memblockq)); + + pa_memblock_unref(chunk.memblock); + /* The next timestamp we expect */ s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size); - - pa_memblock_unref(chunk.memblock); - /* Reset death timer */ - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT); - m->time_restart(s->death_event, &tv); + pa_atomic_store(&s->timestamp, now.tv_sec); + + if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) { + pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix; + unsigned fix_samples; + + pa_log("Updating sample rate"); + + wi = pa_smoother_get(s->smoother, pa_timeval_load(&now)); + ri = pa_bytes_to_usec(pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); + + if (PA_MSGOBJECT(s->sink_input->sink)->process_msg(PA_MSGOBJECT(s->sink_input->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_delay, 0, NULL) < 0) + sink_delay = 0; + + render_delay = pa_bytes_to_usec(pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq), &s->sink_input->sink->sample_spec); + + if (ri > render_delay+sink_delay) + ri -= render_delay+sink_delay; + else + ri = 0; + + if (wi < ri) + latency = 0; + else + latency = wi - ri; + + pa_log_debug("Write index deviates by %0.2f ms, expected %0.2f ms", (double) latency/PA_USEC_PER_MSEC, (double) s->intended_latency/PA_USEC_PER_MSEC); + + /* Calculate deviation */ + if (latency < s->intended_latency) + fix = s->intended_latency - latency; + else + fix = latency - s->intended_latency; + + /* How many samples is this per second? */ + fix_samples = fix * s->sink_input->thread_info.sample_spec.rate / RATE_UPDATE_INTERVAL; + + /* Check if deviation is in bounds */ + if (fix_samples > s->sink_input->sample_spec.rate*.20) + pa_log_debug("Hmmm, rate fix is too large (%lu Hz), not applying.", (unsigned long) fix_samples); + + /* Fix up rate */ + if (latency < s->intended_latency) + s->sink_input->sample_spec.rate -= fix_samples; + else + s->sink_input->sample_spec.rate += fix_samples; + + pa_resampler_set_input_rate(s->sink_input->thread_info.resampler, s->sink_input->sample_spec.rate); + + pa_log_debug("Updated sampling rate to %lu Hz.", (unsigned long) s->sink_input->sample_spec.rate); + + s->last_rate_update = pa_timeval_load(&now); + } + + if (pa_memblockq_is_readable(s->memblockq) && + s->sink_input->thread_info.underrun_for > 0) { + pa_log_debug("Requesting rewind due to end of underrun"); + pa_sink_input_request_rewind(s->sink_input, 0, FALSE, TRUE); + } + + 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); } -static void death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) { - struct session *s = userdata; - - assert(m); - assert(t); - assert(tv); - assert(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); - session_free(s, 1); + 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)); @@ -226,7 +367,7 @@ static int mcast_socket(const struct sockaddr* sa, socklen_t salen) { pa_log("SO_REUSEADDR failed: %s", pa_cstrerror(errno)); goto fail; } - + if (af == AF_INET) { struct ip_mreq mr4; memset(&mr4, 0, sizeof(mr4)); @@ -243,14 +384,14 @@ static int mcast_socket(const struct sockaddr* sa, socklen_t salen) { 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); @@ -260,136 +401,149 @@ fail: static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_info) { struct session *s = NULL; - struct timeval tv; - char *c; pa_sink *sink; int fd = -1; - pa_memblock *silence; + pa_memchunk 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."); + pa_log("Session limit reached."); goto fail; } - - if (!(sink = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) { - pa_log("sink does not exist."); + + if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, TRUE))) { + pa_log("Sink does not exist."); goto fail; } + pa_rtclock_get(&now); + s = pa_xnew0(struct session, 1); s->userdata = u; - s->first_packet = 0; + s->first_packet = FALSE; s->sdp_info = *sdp_info; + s->rtpoll_item = NULL; + s->intended_latency = LATENCY_USEC; + s->smoother = pa_smoother_new(PA_USEC_PER_SEC*5, PA_USEC_PER_SEC*2, TRUE, 10); + pa_smoother_set_time_offset(s->smoother, pa_timeval_load(&now)); + s->last_rate_update = pa_timeval_load(&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; + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "stream"); + pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, + "RTP Stream%s%s%s", + sdp_info->session_name ? " (" : "", + sdp_info->session_name ? sdp_info->session_name : "", + sdp_info->session_name ? ")" : ""); + + if (sdp_info->session_name) + pa_proplist_sets(data.proplist, "rtp.session", sdp_info->session_name); + pa_proplist_sets(data.proplist, "rtp.origin", sdp_info->origin); + pa_proplist_setf(data.proplist, "rtp.payload", "%u", (unsigned) sdp_info->payload); 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->core, &data, 0); - pa_xfree(c); - + + s->sink_input = pa_sink_input_new(u->module->core, &data, 0); + pa_sink_input_new_data_done(&data); + if (!s->sink_input) { - pa_log("failed to create sink input."); + pa_log("Failed to create sink input."); goto fail; } s->sink_input->userdata = s; - s->sink_input->peek = sink_input_peek; - s->sink_input->drop = sink_input_drop; + s->sink_input->parent.process_msg = sink_input_process_msg; + s->sink_input->pop = sink_input_pop_cb; + s->sink_input->process_rewind = sink_input_process_rewind_cb; + s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; s->sink_input->kill = sink_input_kill; - s->sink_input->get_latency = sink_input_get_latency; + s->sink_input->attach = sink_input_attach; + s->sink_input->detach = sink_input_detach; + + pa_sink_input_get_silence(s->sink_input, &silence); + + s->sink_latency = pa_sink_input_set_requested_latency(s->sink_input, s->intended_latency/2); + + if (s->intended_latency < s->sink_latency*2) + s->intended_latency = s->sink_latency*2; - silence = pa_silence_memblock_new(s->userdata->core->mempool, - &s->sink_input->sample_spec, - (pa_bytes_per_second(&s->sink_input->sample_spec)/128/pa_frame_size(&s->sink_input->sample_spec))* - pa_frame_size(&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, + pa_usec_to_bytes(s->intended_latency - s->sink_latency, &s->sink_input->sample_spec), + 0, 0, - silence); + &silence); - pa_memblock_unref(silence); + pa_memblock_unref(silence.memblock); - s->rtp_event = u->core->mainloop->io_new(u->core->mainloop, fd, PA_IO_EVENT_INPUT, rtp_event_cb, s); - - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT); - s->death_event = u->core->mainloop->time_new(u->core->mainloop, &tv, death_event_cb, s); + 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_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec)); + pa_sink_input_put(s->sink_input); - pa_log_info("Found new session '%s'", s->sdp_info.session_name); + pa_log_info("New session '%s'", s->sdp_info.session_name); - u->n_sessions++; - return s; fail: - if (s) { - if (fd >= 0) - close(fd); - - pa_xfree(s); - } + pa_xfree(s); + + if (fd >= 0) + pa_close(fd); return NULL; } -static void session_free(struct session *s, int from_hash) { - assert(s); +static void session_free(struct session *s) { + pa_assert(s); pa_log_info("Freeing session '%s'", s->sdp_info.session_name); - s->userdata->core->mainloop->time_free(s->death_event); - s->userdata->core->mainloop->io_free(s->rtp_event); - - if (from_hash) - pa_hashmap_remove(s->userdata->by_origin, s->sdp_info.origin); - - pa_sink_input_disconnect(s->sink_input); + 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); - assert(s->userdata->n_sessions >= 1); - s->userdata->n_sessions--; - + pa_smoother_free(s->smoother); + 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_bool_t goodbye = FALSE; pa_sdp_info info; struct session *s; - - assert(m); - assert(e); - assert(u); - assert(fd == u->sap_context.fd); - assert(flags == PA_IO_EVENT_INPUT); + + 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; @@ -400,7 +554,7 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event if (goodbye) { if ((s = pa_hashmap_get(u->by_origin, info.origin))) - session_free(s, 1); + session_free(s); pa_sdp_info_destroy(&info); } else { @@ -408,20 +562,49 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event if (!(s = pa_hashmap_get(u->by_origin, info.origin))) { if (!(s = session_new(u, &info))) pa_sdp_info_destroy(&info); - + } else { - struct timeval tv; - - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT); - m->time_restart(s->death_event, &tv); - + struct timeval now; + pa_rtclock_get(&now); + pa_atomic_store(&s->timestamp, now.tv_sec); + pa_sdp_info_destroy(&info); } } } -int pa__init(pa_core *c, pa_module*m) { +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; @@ -430,9 +613,9 @@ int pa__init(pa_core *c, pa_module*m) { socklen_t salen; const char *sap_address; int fd = -1; - - assert(c); - assert(m); + struct timeval tv; + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments"); @@ -440,7 +623,7 @@ int pa__init(pa_core *c, pa_module*m) { } 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); @@ -452,7 +635,7 @@ int pa__init(pa_core *c, pa_module*m) { sa = (struct sockaddr*) &sa4; salen = sizeof(sa4); } else { - pa_log("invalid SAP address '%s'", sap_address); + pa_log("Invalid SAP address '%s'", sap_address); goto fail; } @@ -462,16 +645,19 @@ int pa__init(pa_core *c, pa_module*m) { u = pa_xnew(struct userdata, 1); m->userdata = u; u->module = m; - u->core = c; u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); - u->n_sessions = 0; - u->sap_event = c->mainloop->io_new(c->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u); + 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_sap_context_init_recv(&u->sap_context, fd); - + + 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; @@ -481,28 +667,35 @@ fail: pa_modargs_free(ma); if (fd >= 0) - close(fd); - - return -1; -} + pa_close(fd); -static void free_func(void *p, PA_GCC_UNUSED void *userdata) { - session_free(p, 0); + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + struct session *s; + + pa_assert(m); if (!(u = m->userdata)) return; - c->mainloop->io_free(u->sap_event); + 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); - pa_hashmap_free(u->by_origin, free_func, NULL); - + 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 index 7bbfabee..d0d06c4d 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> @@ -46,6 +45,9 @@ #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" @@ -53,9 +55,10 @@ #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_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> " @@ -65,14 +68,14 @@ PA_MODULE_USAGE( "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 5000000 +#define SAP_INTERVAL 5 static const char* const valid_modargs[] = { "source", @@ -88,7 +91,6 @@ static const char* const valid_modargs[] = { struct userdata { pa_module *module; - pa_core *core; pa_source_output *source_output; pa_memblockq *memblockq; @@ -100,56 +102,67 @@ struct userdata { 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; - assert(o); - u = o->userdata; + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); if (pa_memblockq_push(u->memblockq, chunk) < 0) { - pa_log("Failed to push chunk into memblockq."); + 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; - assert(o); - u = o->userdata; + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); pa_module_unload_request(u->module); - pa_source_output_disconnect(u->source_output); + pa_source_output_unlink(u->source_output); pa_source_output_unref(u->source_output); u->source_output = NULL; } -static pa_usec_t source_output_get_latency (pa_source_output *o) { - struct userdata *u; - assert(o); - u = o->userdata; - - return pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &o->sample_spec); -} - 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; - - assert(m); - assert(t); - assert(tv); - assert(u); + + 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_timeval_add(&next, SAP_INTERVAL * PA_USEC_PER_SEC); m->time_restart(t, &next); } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; const char *dest; @@ -164,28 +177,27 @@ int pa__init(pa_core *c, pa_module*m) { pa_source_output *o = NULL; uint8_t payload; char *p; - int r; + int r, j; socklen_t k; struct timeval tv; char hn[128], *n; - int loop = 0; + pa_bool_t loop = FALSE; pa_source_output_new_data data; - - assert(c); - assert(m); + + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + 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."); + 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."); + pa_log("Failed to parse \"loop\" parameter."); goto fail; } @@ -193,12 +205,12 @@ int pa__init(pa_core *c, pa_module*m) { 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"); + 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"); + pa_log("Specified sample type not compatible with RTP"); goto fail; } @@ -207,10 +219,10 @@ int pa__init(pa_core *c, pa_module*m) { payload = pa_rtp_payload_from_sample_spec(&ss); - mtu = (DEFAULT_MTU/pa_frame_size(&ss))*pa_frame_size(&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."); + pa_log("Invalid MTU."); goto fail; } @@ -221,7 +233,7 @@ int pa__init(pa_core *c, pa_module*m) { } if (port & 1) - pa_log_warn("WARNING: port number not even as suggested in RFC3550!"); + pa_log_warn("Port number not even as suggested in RFC3550!"); dest = pa_modargs_get_value(ma, "destination", DEFAULT_DESTINATION); @@ -236,10 +248,10 @@ int pa__init(pa_core *c, pa_module*m) { sap_sa4 = sa4; sap_sa4.sin_port = htons(SAP_PORT); } else { - pa_log("invalid destination '%s'", dest); + 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; @@ -260,37 +272,49 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0 || - setsockopt(sap_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) { + 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"; + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, "RTP Monitor Stream"); + pa_proplist_sets(data.proplist, "rtp.destination", dest); + pa_proplist_setf(data.proplist, "rtp.mtu", "%lu", (unsigned long) mtu); + pa_proplist_setf(data.proplist, "rtp.port", "%lu", (unsigned long) port); 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(c, &data, 0))) { + + o = pa_source_output_new(m->core, &data, 0); + pa_source_output_new_data_done(&data); + + if (!o) { 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; - o->get_latency = source_output_get_latency; - + u = pa_xnew(struct userdata, 1); m->userdata = u; o->userdata = u; u->module = m; - u->core = c; u->source_output = o; - + u->memblockq = pa_memblockq_new( 0, MEMBLOCKQ_MAXLENGTH, @@ -298,34 +322,36 @@ int pa__init(pa_core *c, pa_module*m) { pa_frame_size(&ss), 1, 0, + 0, NULL); u->mtu = mtu; - + k = sizeof(sa_dst); - r = getsockname(fd, (struct sockaddr*) &sa_dst, &k); - assert(r >= 0); + 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, c->cookie, payload, pa_frame_size(&ss)); + + 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\n"__FILE__": EOF", p); + 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); - u->sap_event = c->mainloop->time_new(c->mainloop, &tv, sap_event_cb, u); + 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); @@ -336,31 +362,31 @@ fail: pa_modargs_free(ma); if (fd >= 0) - close(fd); - + pa_close(fd); + if (sap_fd >= 0) - close(sap_fd); + pa_close(sap_fd); if (o) { - pa_source_output_disconnect(o); + pa_source_output_unlink(o); pa_source_output_unref(o); } - + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata *u; - assert(c); - assert(m); + pa_assert(m); if (!(u = m->userdata)) return; - c->mainloop->time_free(u->sap_event); - + if (u->sap_event) + m->core->mainloop->time_free(u->sap_event); + if (u->source_output) { - pa_source_output_disconnect(u->source_output); + pa_source_output_unlink(u->source_output); pa_source_output_unref(u->source_output); } @@ -369,7 +395,8 @@ void pa__done(pa_core *c, pa_module*m) { pa_sap_send(&u->sap_context, 1); pa_sap_context_destroy(&u->sap_context); - pa_memblockq_free(u->memblockq); - + if (u->memblockq) + pa_memblockq_free(u->memblockq); + pa_xfree(u); } diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index a4362f84..5a33ebc2 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> @@ -38,12 +37,14 @@ #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) { - assert(c); - assert(fd >= 0); + pa_assert(c); + pa_assert(fd >= 0); c->fd = fd; c->sequence = (uint16_t) (rand()*rand()); @@ -51,7 +52,9 @@ pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssr c->ssrc = ssrc ? ssrc : (uint32_t) (rand()*rand()); c->payload = payload & 127; c->frame_size = frame_size; - + + pa_memchunk_reset(&c->memchunk); + return c; } @@ -61,37 +64,39 @@ 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, skip = 0; - - assert(c); - assert(size > 0); - assert(q); + 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; - if (chunk.memblock) { - iov[iov_idx].iov_base = (void*)((uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index); - iov[iov_idx].iov_len = k; - mb[iov_idx] = chunk.memblock; - iov_idx ++; + pa_assert(chunk.memblock); - n += k; - } + 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 ++; - skip += k; - pa_memblockq_drop(q, &chunk, k); + n += k; + pa_memblockq_drop(q, k); } - if (r < 0 || !chunk.memblock || n >= size || iov_idx >= MAX_IOVECS) { + 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; @@ -103,7 +108,7 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { iov[0].iov_base = (void*)header; iov[0].iov_len = sizeof(header); - + m.msg_name = NULL; m.msg_namelen = 0; m.msg_iov = iov; @@ -111,7 +116,7 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { 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++) { @@ -123,19 +128,18 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { } else k = 0; - c->timestamp += skip/c->frame_size; - + c->timestamp += n/c->frame_size; + if (k < 0) { - if (errno != EAGAIN) /* If the queue is full, just ignore it */ + 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; - skip = 0; iov_idx = 1; } } @@ -144,10 +148,12 @@ 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) { - assert(c); + pa_assert(c); c->fd = fd; c->frame_size = frame_size; + + pa_memchunk_reset(&c->memchunk); return c; } @@ -158,23 +164,39 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { uint32_t header; int cc; ssize_t r; - - assert(c); - assert(chunk); - chunk->memblock = NULL; + pa_assert(c); + pa_assert(chunk); + + pa_memchunk_reset(chunk); if (ioctl(c->fd, FIONREAD, &size) < 0) { - pa_log("FIONREAD failed: %s", pa_cstrerror(errno)); + pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno)); goto fail; } - if (!size) + if (size <= 0) return 0; - chunk->memblock = pa_memblock_new(pool, size); + if (c->memchunk.length < (unsigned) size) { + size_t l; + + if (c->memchunk.memblock) + pa_memblock_unref(c->memchunk.memblock); - iov.iov_base = pa_memblock_acquire(chunk->memblock); + l = PA_MAX((size_t) size, pa_mempool_block_size_max(pool)); + + c->memchunk.memblock = pa_memblock_new(pool, l); + c->memchunk.index = 0; + c->memchunk.length = pa_memblock_get_length(c->memchunk.memblock); + } + + pa_assert(c->memchunk.length >= (size_t) size); + + chunk->memblock = pa_memblock_ref(c->memchunk.memblock); + chunk->index = c->memchunk.index; + + iov.iov_base = (uint8_t*) pa_memblock_acquire(chunk->memblock) + chunk->index; iov.iov_len = size; m.msg_name = NULL; @@ -184,37 +206,42 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { m.msg_control = NULL; m.msg_controllen = 0; m.msg_flags = 0; - - if ((r = recvmsg(c->fd, &m, 0)) != size) { - pa_log("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); + + 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("RTP packet too short."); + 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("Unsupported RTP version."); + pa_log_warn("Unsupported RTP version."); goto fail; } if ((header >> 29) & 1) { - pa_log("RTP padding not supported."); + pa_log_warn("RTP padding not supported."); goto fail; } if ((header >> 28) & 1) { - pa_log("RTP header extensions not supported."); + pa_log_warn("RTP header extensions not supported."); goto fail; } @@ -223,31 +250,37 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { c->sequence = header & 0xFFFF; if (12 + cc*4 > size) { - pa_log("RTP packet too short. (CSRC)"); + pa_log_warn("RTP packet too short. (CSRC)"); goto fail; } - chunk->index = 12 + cc*4; - chunk->length = size - chunk->index; + chunk->index += 12 + cc*4; + chunk->length = size - 12 + cc*4; if (chunk->length % c->frame_size != 0) { - pa_log("Vad RTP packet size."); + pa_log_warn("Bad RTP packet size."); goto fail; } - + + c->memchunk.index = chunk->index + chunk->length; + c->memchunk.length = pa_memblock_get_length(c->memchunk.memblock) - c->memchunk.index; + + if (c->memchunk.length <= 0) { + pa_memblock_unref(c->memchunk.memblock); + pa_memchunk_reset(&c->memchunk); + } + return 0; fail: - if (chunk->memblock) { - pa_memblock_release(chunk->memblock); + if (chunk->memblock) pa_memblock_unref(chunk->memblock); - } return -1; } uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) { - assert(ss); + pa_assert(ss); if (ss->format == PA_SAMPLE_ULAW && ss->rate == 8000 && ss->channels == 1) return 0; @@ -257,12 +290,12 @@ uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) { 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) { - assert(ss); + pa_assert(ss); switch (payload) { case 0: @@ -282,7 +315,7 @@ pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec ss->format = PA_SAMPLE_S16BE; ss->rate = 44100; break; - + case 11: ss->channels = 1; ss->format = PA_SAMPLE_S16BE; @@ -297,17 +330,17 @@ pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec } pa_sample_spec *pa_rtp_sample_spec_fixup(pa_sample_spec * ss) { - assert(ss); + pa_assert(ss); if (!pa_rtp_sample_spec_valid(ss)) ss->format = PA_SAMPLE_S16BE; - assert(pa_rtp_sample_spec_valid(ss)); + pa_assert(pa_rtp_sample_spec_valid(ss)); return ss; } int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) { - assert(ss); + pa_assert(ss); if (!pa_sample_spec_valid(ss)) return 0; @@ -320,9 +353,12 @@ int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) { } void pa_rtp_context_destroy(pa_rtp_context *c) { - assert(c); + pa_assert(c); + + pa_assert_se(pa_close(c->fd) == 0); - close(c->fd); + if (c->memchunk.memblock) + pa_memblock_unref(c->memchunk.memblock); } const char* pa_rtp_format_to_string(pa_sample_format_t f) { @@ -341,8 +377,8 @@ const char* pa_rtp_format_to_string(pa_sample_format_t f) { } pa_sample_format_t pa_rtp_string_to_format(const char *s) { - assert(s); - + pa_assert(s); + if (!(strcmp(s, "L16"))) return PA_SAMPLE_S16BE; else if (!strcmp(s, "L8")) @@ -354,4 +390,3 @@ pa_sample_format_t pa_rtp_string_to_format(const char *s) { else return PA_SAMPLE_INVALID; } - diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h index 123602b2..a2728f05 100644 --- a/src/modules/rtp/rtp.h +++ b/src/modules/rtp/rtp.h @@ -1,21 +1,21 @@ #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 @@ -35,6 +35,8 @@ typedef struct pa_rtp_context { uint32_t ssrc; uint8_t payload; size_t frame_size; + + pa_memchunk memchunk; } 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); diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c index 022c7fa3..5d9b58fa 100644 --- a/src/modules/rtp/sap.c +++ b/src/modules/rtp/sap.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <time.h> #include <stdlib.h> #include <sys/types.h> @@ -44,6 +43,7 @@ #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "sap.h" #include "sdp.h" @@ -51,25 +51,25 @@ #define MIME_TYPE "application/sdp" pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data) { - assert(c); - assert(fd >= 0); - assert(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; + + return c; } void pa_sap_context_destroy(pa_sap_context *c) { - assert(c); + pa_assert(c); - close(c->fd); + pa_close(c->fd); pa_xfree(c->sdp_data); } -int pa_sap_send(pa_sap_context *c, int goodbye) { +int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye) { uint32_t header; struct sockaddr_storage sa_buf; struct sockaddr *sa = (struct sockaddr*) &sa_buf; @@ -83,8 +83,8 @@ int pa_sap_send(pa_sap_context *c, int goodbye) { return -1; } - assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); - + 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) | @@ -101,7 +101,7 @@ int pa_sap_send(pa_sap_context *c, int goodbye) { 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; @@ -109,23 +109,23 @@ int pa_sap_send(pa_sap_context *c, int goodbye) { m.msg_control = NULL; m.msg_controllen = 0; m.msg_flags = 0; - + if ((k = sendmsg(c->fd, &m, MSG_DONTWAIT)) < 0) - pa_log("sendmsg() failed: %s\n", pa_cstrerror(errno)); + 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) { - assert(c); - assert(fd >= 0); + 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) { +int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye) { struct msghdr m; struct iovec iov; int size, k; @@ -133,21 +133,18 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { uint32_t header; int six, ac; ssize_t r; - - assert(c); - assert(goodbye); + + pa_assert(c); + pa_assert(goodbye); if (ioctl(c->fd, FIONREAD, &size) < 0) { - pa_log("FIONREAD failed: %s", pa_cstrerror(errno)); + pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno)); goto fail; } - if (!size) - return 0; - buf = pa_xnew(char, size+1); buf[size] = 0; - + iov.iov_base = buf; iov.iov_len = size; @@ -158,14 +155,14 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { m.msg_control = NULL; m.msg_controllen = 0; m.msg_flags = 0; - + if ((r = recvmsg(c->fd, &m, 0)) != size) { - pa_log("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); + pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); goto fail; } if (size < 4) { - pa_log("SAP packet too short."); + pa_log_warn("SAP packet too short."); goto fail; } @@ -173,17 +170,17 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { header = ntohl(header); if (header >> 29 != 1) { - pa_log("Unsupported SAP version."); + pa_log_warn("Unsupported SAP version."); goto fail; } if ((header >> 25) & 1) { - pa_log("Encrypted SAP not supported."); + pa_log_warn("Encrypted SAP not supported."); goto fail; } if ((header >> 24) & 1) { - pa_log("Compressed SAP not supported."); + pa_log_warn("Compressed SAP not supported."); goto fail; } @@ -192,7 +189,7 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { k = 4 + (six ? 16 : 4) + ac*4; if (size < k) { - pa_log("SAP packet too short (AD)."); + pa_log_warn("SAP packet too short (AD)."); goto fail; } @@ -203,18 +200,18 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) { 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("Invalid SDP header."); + 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: diff --git a/src/modules/rtp/sap.h b/src/modules/rtp/sap.h index 987403e3..69c757cb 100644 --- a/src/modules/rtp/sap.h +++ b/src/modules/rtp/sap.h @@ -1,21 +1,21 @@ #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 @@ -38,9 +38,9 @@ typedef struct 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); +int pa_sap_send(pa_sap_context *c, pa_bool_t 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); +int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye); #endif diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c index 1b71a9a0..cef90433 100644 --- a/src/modules/rtp/sdp.c +++ b/src/modules/rtp/sdp.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <time.h> #include <stdlib.h> #include <sys/types.h> @@ -33,37 +32,34 @@ #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]; + char buf_src[64], buf_dst[64], un[64]; const char *u, *f, *a; - assert(src); - assert(dst); - assert(af == AF_INET || af == AF_INET6); - - f = pa_rtp_format_to_string(ss->format); - assert(f); - - if (!(u = getenv("USER"))) - if (!(u = getenv("USERNAME"))) - u = "-"; - + 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; - a = inet_ntop(af, src, buf_src, sizeof(buf_src)); - assert(a); - a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst)); - assert(a); - + 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" @@ -84,8 +80,8 @@ char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, u static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) { unsigned rate, channels; - assert(ss); - assert(c); + pa_assert(ss); + pa_assert(c); if (pa_startswith(c, "L16/")) { ss->format = PA_SAMPLE_S16BE; @@ -119,15 +115,15 @@ static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) { 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_bool_t ss_valid = FALSE; + + pa_assert(t); + pa_assert(i); - assert(t); - 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; @@ -154,7 +150,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { size_t k; k = l-8 > sizeof(a) ? sizeof(a) : l-8; - + pa_strlcpy(a, t+9, k); a[strcspn(a, "/")] = 0; @@ -171,7 +167,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { size_t k; k = l-8 > sizeof(a) ? sizeof(a) : l-8; - + pa_strlcpy(a, t+9, k); a[strcspn(a, "/")] = 0; @@ -187,7 +183,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { if (i->payload > 127) { int _port, _payload; - + if (sscanf(t+8, "%i RTP/AVP %i", &_port, &_payload) == 2) { if (_port <= 0 || _port > 0xFFFF) { @@ -204,7 +200,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { i->payload = (uint8_t) _payload; if (pa_rtp_sample_spec_from_payload(i->payload, &i->sample_spec)) - ss_valid = 1; + ss_valid = TRUE; } } } else if (pa_startswith(t, "a=rtpmap:")) { @@ -222,16 +218,16 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { if (_payload == i->payload) { c[strcspn(c, "\n")] = 0; - + if (parse_sdp_sample_spec(&i->sample_spec, c)) - ss_valid = 1; + ss_valid = TRUE; } } } } - + t += l; - + if (*t == '\n') t++; } @@ -245,7 +241,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { ((struct sockaddr_in*) &i->sa)->sin_port = htons(port); else ((struct sockaddr_in6*) &i->sa)->sin6_port = htons(port); - + return i; fail: @@ -256,7 +252,7 @@ fail: } void pa_sdp_info_destroy(pa_sdp_info *i) { - assert(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 index b95ca633..933a602b 100644 --- a/src/modules/rtp/sdp.h +++ b/src/modules/rtp/sdp.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulse/.gitignore b/src/pulse/.gitignore new file mode 100644 index 00000000..67020331 --- /dev/null +++ b/src/pulse/.gitignore @@ -0,0 +1 @@ +version.h diff --git a/src/pulse/browser.c b/src/pulse/browser.c index dae8e3d5..1a3f657f 100644 --- a/src/pulse/browser.c +++ b/src/pulse/browser.c @@ -1,29 +1,28 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write 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_CONFIG_H +#include "config.h" +#endif -#include <assert.h> #include <string.h> #include <avahi-client/lookup.h> @@ -34,8 +33,9 @@ #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" @@ -44,7 +44,8 @@ #define SERVICE_TYPE_SERVER "_pulse-server._tcp." struct pa_browser { - int ref; + PA_REFCNT_DECLARE; + pa_mainloop_api *mainloop; AvahiPoll* avahi_poll; @@ -53,13 +54,14 @@ struct pa_browser { 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)) @@ -84,7 +86,7 @@ static void resolve_callback( AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { - + pa_browser *b = userdata; pa_browse_info i; char ip[256], a[256]; @@ -94,35 +96,36 @@ static void resolve_callback( pa_sample_spec ss; int ss_valid = 0; char *key = NULL, *value = NULL; - - assert(b); + + 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); - assert(opcode >= 0); + pa_assert(opcode >= 0); if (aa->proto == AVAHI_PROTO_INET) - snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port); + pa_snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port); else { - assert(aa->proto == AVAHI_PROTO_INET6); - snprintf(a, sizeof(a), "tcp6:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port); + 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); @@ -138,20 +141,20 @@ static void resolve_callback( value = NULL; } else if (!strcmp(key, "fqdn")) { size_t l; - + pa_xfree((char*) i.fqdn); i.fqdn = value; value = NULL; - + l = strlen(a); - assert(l+1 <= sizeof(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); @@ -159,13 +162,13 @@ static void resolve_callback( 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; @@ -174,7 +177,7 @@ static void resolve_callback( if ((ss.format = pa_parse_sample_format(value)) == PA_SAMPLE_INVALID) goto fail; - + ss_valid |= 4; } @@ -186,7 +189,7 @@ static void resolve_callback( } /* No device txt record was sent for a sink or source service */ - if (opcode != PA_BROWSE_NEW_SERVER && !device_found) + if (opcode != PA_BROWSE_NEW_SERVER && !device_found) goto fail; if (ss_valid == 7) @@ -203,13 +206,15 @@ fail: pa_xfree(key); pa_xfree(value); - + avahi_service_resolver_free(r); } static void handle_failure(pa_browser *b) { const char *e = NULL; - assert(b); + + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); if (b->sink_browser) avahi_service_browser_free(b->sink_browser); @@ -243,7 +248,9 @@ static void browse_callback( void *userdata) { pa_browser *b = userdata; - assert(b); + + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); switch (event) { case AVAHI_BROWSER_NEW: { @@ -263,19 +270,19 @@ static void browse_callback( 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); - assert(opcode >= 0); - + pa_assert(opcode >= 0); + b->callback(b, opcode, &i, b->userdata); } break; @@ -285,7 +292,7 @@ static void browse_callback( handle_failure(b); break; } - + default: ; } @@ -293,7 +300,10 @@ static void browse_callback( static void client_callback(AvahiClient *s, AvahiClientState state, void *userdata) { pa_browser *b = userdata; - assert(s); + + pa_assert(s); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); if (state == AVAHI_CLIENT_FAILURE) handle_failure(b); @@ -301,22 +311,27 @@ static void client_callback(AvahiClient *s, AvahiClientState state, void *userda static void browser_free(pa_browser *b); + +PA_WARN_REFERENCE(pa_browser_new, "libpulse-browse is being phased out."); + 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_WARN_REFERENCE(pa_browser_new_full, "libpulse-browse is being phased out."); + pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) { pa_browser *b; int error; - assert(mainloop); + 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; - b->ref = 1; + PA_REFCNT_INIT(b); b->callback = NULL; b->userdata = NULL; b->error_callback = NULL; @@ -335,7 +350,7 @@ pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t fla !(b->server_browser = avahi_service_browser_new( b->client, AVAHI_IF_UNSPEC, - AVAHI_PROTO_UNSPEC, + AVAHI_PROTO_INET, SERVICE_TYPE_SERVER, NULL, 0, @@ -346,7 +361,7 @@ pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t fla *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, @@ -378,18 +393,19 @@ pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t fla *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) { - assert(b && b->mainloop); + pa_assert(b); + pa_assert(b->mainloop); if (b->sink_browser) avahi_service_browser_free(b->sink_browser); @@ -403,34 +419,45 @@ static void browser_free(pa_browser *b) { if (b->avahi_poll) pa_avahi_poll_free(b->avahi_poll); - + pa_xfree(b); } +PA_WARN_REFERENCE(pa_browser_ref, "libpulse-browse is being phased out."); + pa_browser *pa_browser_ref(pa_browser *b) { - assert(b); - assert(b->ref >= 1); - b->ref++; + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + PA_REFCNT_INC(b); return b; } +PA_WARN_REFERENCE(pa_browser_unref, "libpulse-browse is being phased out."); + void pa_browser_unref(pa_browser *b) { - assert(b); - assert(b->ref >= 1); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); - if ((-- (b->ref)) <= 0) + if (PA_REFCNT_DEC(b) <= 0) browser_free(b); } +PA_WARN_REFERENCE(pa_browser_set_callback, "libpulse-browse is being phased out."); + void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) { - assert(b); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); b->callback = cb; b->userdata = userdata; } +PA_WARN_REFERENCE(pa_browser_set_error_callback, "libpulse-browse is being phased out."); + void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) { - assert(b); + 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 index fc57a4d5..c4e0a17e 100644 --- a/src/pulse/browser.h +++ b/src/pulse/browser.h @@ -1,21 +1,21 @@ #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 @@ -41,7 +41,7 @@ typedef enum pa_browse_opcode { 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_SINK, /**< Sink disappeared */ PA_BROWSE_REMOVE_SOURCE /**< Source disappeared */ } pa_browse_opcode_t; diff --git a/src/pulse/cdecl.h b/src/pulse/cdecl.h index a3ec231c..8c5b2d0f 100644 --- a/src/pulse/cdecl.h +++ b/src/pulse/cdecl.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c index 69b09089..7348b32e 100644 --- a/src/pulse/channelmap.c +++ b/src/pulse/channelmap.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,34 +25,34 @@ #endif #include <stdlib.h> -#include <assert.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[] = { +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", @@ -86,19 +87,82 @@ const char *const table[] = { [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_FRONT_CENTER] = "top-front-center", + [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_POSITION_TOP_REAR_CENTER] = "top-rear-center" + [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; - assert(m); + pa_assert(m); m->channels = 0; @@ -109,7 +173,7 @@ pa_channel_map* pa_channel_map_init(pa_channel_map *m) { } pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m) { - assert(m); + pa_assert(m); pa_channel_map_init(m); @@ -119,7 +183,7 @@ pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m) { } pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m) { - assert(m); + pa_assert(m); pa_channel_map_init(m); @@ -130,9 +194,9 @@ pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m) { } pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { - assert(m); - assert(channels > 0); - assert(channels <= PA_CHANNELS_MAX); + pa_assert(m); + pa_assert(channels > 0); + pa_assert(channels <= PA_CHANNELS_MAX); pa_channel_map_init(m); @@ -140,14 +204,14 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p 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; @@ -156,31 +220,31 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p 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; } @@ -191,43 +255,43 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p 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; } @@ -237,55 +301,55 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p 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; } @@ -296,12 +360,12 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p 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; @@ -310,26 +374,54 @@ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, p 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; } } +pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { + unsigned c; + + pa_assert(m); + pa_assert(channels > 0); + pa_assert(channels <= PA_CHANNELS_MAX); + + pa_channel_map_init(m); + + for (c = channels; c > 0; c--) { + + if (pa_channel_map_init_auto(m, c, def)) { + unsigned i = 0; + + for (; c < channels; c++) { + + m->map[c] = PA_CHANNEL_POSITION_AUX0 + i; + i++; + } + + m->channels = channels; + + return m; + } + } + + return NULL; +} const char* pa_channel_position_to_string(pa_channel_position_t pos) { @@ -339,15 +431,22 @@ const char* pa_channel_position_to_string(pa_channel_position_t pos) { 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; - - assert(a); - assert(b); + + 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; @@ -359,15 +458,15 @@ char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) { unsigned channel; int first = 1; char *e; - - assert(s); - assert(l > 0); - assert(map); + + pa_assert(s); + pa_assert(l > 0); + pa_assert(map); *(e = s) = 0; for (channel = 0; channel < map->channels && l > 1; channel++) { - l -= snprintf(e, l, "%s%s", + l -= pa_snprintf(e, l, "%s%s", first ? "" : ",", pa_channel_position_to_string(map->map[channel])); @@ -382,9 +481,9 @@ pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { const char *state; pa_channel_map map; char *p; - - assert(rmap); - assert(s); + + pa_assert(rmap); + pa_assert(s); memset(&map, 0, sizeof(map)); @@ -397,14 +496,14 @@ pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { 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; @@ -416,13 +515,13 @@ pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { 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; @@ -433,24 +532,24 @@ pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { } 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; - - assert(map); + + 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; @@ -458,4 +557,3 @@ int pa_channel_map_valid(const pa_channel_map *map) { return 1; } - diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h index 8a39ade8..2551eae9 100644 --- a/src/pulse/channelmap.h +++ b/src/pulse/channelmap.h @@ -1,21 +1,22 @@ #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 @@ -24,6 +25,7 @@ #include <pulse/sample.h> #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> /** \page channelmap Channel Maps * @@ -72,7 +74,7 @@ typedef enum pa_channel_position { 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, @@ -80,13 +82,13 @@ typedef enum pa_channel_position { 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, @@ -124,7 +126,7 @@ typedef enum pa_channel_position { 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, @@ -132,7 +134,7 @@ typedef enum pa_channel_position { 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; @@ -143,7 +145,7 @@ typedef enum pa_channel_map_def { 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; @@ -164,12 +166,23 @@ 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. */ +/** Initialize the specified channel map for the specified number of + * channels using default labels and return a pointer to it. This call + * will fail (return NULL) if there is no default channel map known for this + * specific number of channels and mapping. */ pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def); +/** Similar to pa_channel_map_init_auto() but instead of failing if no + * default mapping is known with the specified parameters it will + * synthesize a mapping based on a known mapping with fewer channels + * and fill up the rest with AUX0...AUX31 channels \since 0.9.11 */ +pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def); + /** Return a text label for the specified channel position */ -const char* pa_channel_position_to_string(pa_channel_position_t pos); +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 @@ -177,14 +190,14 @@ const char* pa_channel_position_to_string(pa_channel_position_t pos); /** 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 */ +/** Parse a channel position list into a channel map structure. */ 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); +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); +int pa_channel_map_valid(const pa_channel_map *map) PA_GCC_PURE; PA_C_DECL_END diff --git a/src/pulse/client-conf-x11.c b/src/pulse/client-conf-x11.c index 8cedc48b..393a7cd3 100644 --- a/src/pulse/client-conf-x11.c +++ b/src/pulse/client-conf-x11.c @@ -1,8 +1,8 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, @@ -24,7 +24,6 @@ #endif #include <string.h> -#include <assert.h> #include <X11/Xlib.h> #include <X11/Xatom.h> @@ -34,6 +33,7 @@ #include <pulsecore/x11prop.h> #include <pulsecore/log.h> #include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "client-conf-x11.h" @@ -42,9 +42,14 @@ int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) { int ret = -1; char t[1024]; - if (!dname && !getenv("DISPLAY")) + pa_assert(c); + + if (!dname && !(dname = getenv("DISPLAY"))) goto finish; - + + if (*dname == 0) + goto finish; + if (!(d = XOpenDisplay(dname))) { pa_log("XOpenDisplay() failed"); goto finish; @@ -73,10 +78,10 @@ int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) { goto finish; } - assert(sizeof(cookie) == sizeof(c->cookie)); + pa_assert(sizeof(cookie) == sizeof(c->cookie)); memcpy(c->cookie, cookie, sizeof(cookie)); - c->cookie_valid = 1; + c->cookie_valid = TRUE; pa_xfree(c->cookie_file); c->cookie_file = NULL; @@ -89,5 +94,5 @@ finish: XCloseDisplay(d); return ret; - + } diff --git a/src/pulse/client-conf-x11.h b/src/pulse/client-conf-x11.h index 02e808be..f2f40e03 100644 --- a/src/pulse/client-conf-x11.h +++ b/src/pulse/client-conf-x11.h @@ -1,11 +1,11 @@ #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, diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c index 5cd7e3ed..2ead871f 100644 --- a/src/pulse/client-conf.c +++ b/src/pulse/client-conf.c @@ -1,8 +1,9 @@ -/* $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, @@ -24,14 +25,14 @@ #endif #include <stdlib.h> -#include <assert.h> #include <unistd.h> #include <errno.h> #include <string.h> -#include <pulsecore/core-error.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> @@ -39,13 +40,7 @@ #include "client-conf.h" -#ifndef OS_IS_WIN32 -# define PATH_SEP "/" -#else -# define PATH_SEP "\\" -#endif - -#define DEFAULT_CLIENT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PATH_SEP "client.conf" +#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" @@ -61,24 +56,24 @@ static const pa_client_conf default_conf = { .default_sink = NULL, .default_source = NULL, .default_server = NULL, - .autospawn = 0, - .disable_shm = 0, + .autospawn = TRUE, + .disable_shm = FALSE, .cookie_file = NULL, - .cookie_valid = 0, + .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) { - assert(c); + pa_assert(c); pa_xfree(c->daemon_binary); pa_xfree(c->extra_arguments); pa_xfree(c->default_sink); @@ -87,6 +82,7 @@ void pa_client_conf_free(pa_client_conf *c) { 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; @@ -114,33 +110,39 @@ int pa_client_conf_load(pa_client_conf *c, const char *filename) { 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 (filename) { + + if (!(f = fopen(filename, "r"))) { + pa_log("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } - if (!f && errno != EINTR) { - pa_log("WARNING: failed to open configuration file '%s': %s", fn, pa_cstrerror(errno)); - goto finish; + fn = pa_xstrdup(fn); + + } else { + + if (!(f = pa_open_config_file(DEFAULT_CLIENT_CONFIG_FILE, DEFAULT_CLIENT_CONFIG_FILE_USER, ENV_CLIENT_CONFIG_FILE, &fn))) + if (errno != ENOENT) + 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); @@ -155,7 +157,7 @@ int pa_client_conf_env(pa_client_conf *c) { 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); @@ -167,22 +169,21 @@ int pa_client_conf_env(pa_client_conf *c) { return pa_client_conf_load_cookie(c); } - + return 0; } int pa_client_conf_load_cookie(pa_client_conf* c) { - assert(c); - - c->cookie_valid = 0; + pa_assert(c); if (!c->cookie_file) return -1; + c->cookie_valid = FALSE; + if (pa_authkey_load_auto(c->cookie_file, c->cookie, sizeof(c->cookie)) < 0) return -1; - c->cookie_valid = 1; + c->cookie_valid = TRUE; return 0; } - diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h index 35728aeb..699279aa 100644 --- a/src/pulse/client-conf.h +++ b/src/pulse/client-conf.h @@ -1,11 +1,11 @@ #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, @@ -28,9 +28,9 @@ typedef struct pa_client_conf { char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *cookie_file; - int autospawn, disable_shm; + pa_bool_t autospawn, disable_shm; uint8_t cookie[PA_NATIVE_COOKIE_LENGTH]; - int cookie_valid; /* non-zero, when cookie is valid */ + 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 */ diff --git a/src/pulse/client.conf.in b/src/pulse/client.conf.in index 3cfd9760..749e9688 100644 --- a/src/pulse/client.conf.in +++ b/src/pulse/client.conf.in @@ -1,5 +1,3 @@ -# $Id$ -# # This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify @@ -17,29 +15,18 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. -## Configuration file for pulseaudio clients. Default values are -## commented out. Use either ; or # for commenting - -## Path to the pulseaudio daemon to run when autospawning. -; daemon-binary = @PA_BINARY@ - -## Extra arguments to pass to the pulseaudio daemon -; extra-arguments = --log-target=syslog --exit-idle-time=5 +## Configuration file for PulseAudio clients. See pulse-client.conf(5) for +## more information. Default values a commented out. Use either ; or # for +## commenting. -## The default sink to connect to -; default-sink = - -## The default source to connect to +; default-sink = ; default-source = - -## The default sever to connect to ; default-server = -## Autospawn daemons? -; autospawn = 0 +; autospawn = yes +; daemon-binary = @PA_BINARY@ +; extra-arguments = --log-target=syslog --exit-idle-time=5 -### Cookie file -; cookie-file = +; cookie-file = -### Disable shared memory data transfer -; disable-shm = 0 +; disable-shm = no diff --git a/src/pulse/context.c b/src/pulse/context.c index a458c6b1..f56cb241 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -1,18 +1,19 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +25,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> @@ -33,6 +33,7 @@ #include <errno.h> #include <signal.h> #include <limits.h> +#include <locale.h> #ifdef HAVE_SYS_WAIT_H #include <sys/wait.h> @@ -48,11 +49,13 @@ #include <netdb.h> #endif -#include "../pulsecore/winsock.h" - -#include <pulsecore/core-error.h> #include <pulse/version.h> #include <pulse/xmalloc.h> +#include <pulse/utf8.h> +#include <pulse/util.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> #include <pulsecore/native-common.h> #include <pulsecore/pdispatch.h> @@ -64,6 +67,8 @@ #include <pulsecore/log.h> #include <pulsecore/socket-util.h> #include <pulsecore/creds.h> +#include <pulsecore/macro.h> +#include <pulsecore/proplist-util.h> #include "internal.h" @@ -83,17 +88,26 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_UNDERFLOW] = pa_command_overflow_or_underflow, [PA_COMMAND_PLAYBACK_STREAM_KILLED] = pa_command_stream_killed, [PA_COMMAND_RECORD_STREAM_KILLED] = pa_command_stream_killed, + [PA_COMMAND_PLAYBACK_STREAM_MOVED] = pa_command_stream_moved, + [PA_COMMAND_RECORD_STREAM_MOVED] = pa_command_stream_moved, + [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = pa_command_stream_suspended, + [PA_COMMAND_RECORD_STREAM_SUSPENDED] = pa_command_stream_suspended, + [PA_COMMAND_STARTED] = pa_command_stream_started, [PA_COMMAND_SUBSCRIBE_EVENT] = pa_command_subscribe_event }; static void unlock_autospawn_lock_file(pa_context *c) { - assert(c); - + pa_assert(c); + if (c->autospawn_lock_fd >= 0) { - char lf[PATH_MAX]; - pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf)); - + char *lf; + + if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) + pa_log_warn("Cannot unlock autospawn because runtime path is no more."); + pa_unlock_lockfile(lf, c->autospawn_lock_fd); + pa_xfree(lf); + c->autospawn_lock_fd = -1; } } @@ -101,53 +115,72 @@ static void unlock_autospawn_lock_file(pa_context *c) { static void context_free(pa_context *c); pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) { + return pa_context_new_with_proplist(mainloop, name, NULL); +} + +static void reset_callbacks(pa_context *c) { + pa_assert(c); + + c->state_callback = NULL; + c->state_userdata = NULL; + + c->subscribe_callback = NULL; + c->subscribe_userdata = NULL; +} + +pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, pa_proplist *p) { pa_context *c; - - assert(mainloop); - assert(name); - + + pa_assert(mainloop); + + if (!name && !pa_proplist_contains(p, PA_PROP_APPLICATION_NAME)) + return NULL; + c = pa_xnew(pa_context, 1); - c->ref = 1; - c->name = pa_xstrdup(name); + PA_REFCNT_INIT(c); + + c->proplist = p ? pa_proplist_copy(p) : pa_proplist_new(); + + if (name) + pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, 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(); + c->client_index = PA_INVALID_INDEX; 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; + reset_callbacks(c); - c->is_local = -1; + c->is_local = FALSE; 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; + c->do_autospawn = FALSE; + c->do_shm = FALSE; #ifndef MSG_NOSIGNAL -#ifdef SIGPIPE +#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_load(c->conf, NULL); pa_client_conf_env(c->conf); if (!(c->mempool = pa_mempool_new(!c->conf->disable_shm))) { @@ -164,26 +197,48 @@ pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) { return c; } -static void context_free(pa_context *c) { - assert(c); +static void context_unlink(pa_context *c) { + pa_stream *s; - unlock_autospawn_lock_file(c); + pa_assert(c); + + s = c->streams ? pa_stream_ref(c->streams) : NULL; + while (s) { + pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL; + pa_stream_set_state(s, c->state == PA_CONTEXT_FAILED ? PA_STREAM_FAILED : PA_STREAM_TERMINATED); + pa_stream_unref(s); + s = n; + } while (c->operations) pa_operation_cancel(c->operations); - while (c->streams) - pa_stream_set_state(c->streams, PA_STREAM_TERMINATED); - - if (c->client) - pa_socket_client_unref(c->client); - if (c->pdispatch) + if (c->pdispatch) { pa_pdispatch_unref(c->pdispatch); + c->pdispatch = NULL; + } + if (c->pstream) { - pa_pstream_close(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; } - + + reset_callbacks(c); +} + +static void context_free(pa_context *c) { + pa_assert(c); + + context_unlink(c); + + unlock_autospawn_lock_file(c); + if (c->record_streams) pa_dynarray_free(c->record_streams, NULL, NULL); if (c->playback_streams) @@ -196,81 +251,53 @@ static void context_free(pa_context *c) { pa_client_conf_free(c->conf); pa_strlist_free(c->server_list); - - pa_xfree(c->name); + + if (c->proplist) + pa_proplist_free(c->proplist); + pa_xfree(c->server); pa_xfree(c); } pa_context* pa_context_ref(pa_context *c) { - assert(c); - assert(c->ref >= 1); - - c->ref++; + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_REFCNT_INC(c); return c; } void pa_context_unref(pa_context *c) { - assert(c); - assert(c->ref >= 1); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); - if (--c->ref <= 0) + if (PA_REFCNT_DEC(c) <= 0) context_free(c); } void pa_context_set_state(pa_context *c, pa_context_state_t st) { - assert(c); - assert(c->ref >= 1); - + 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_close(c->pstream); - pa_pstream_unref(c->pstream); - } - c->pstream = NULL; - - if (c->client) - pa_socket_client_unref(c->client); - c->client = NULL; - } + if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED) + context_unlink(c); pa_context_unref(c); } -void pa_context_fail(pa_context *c, int error) { - assert(c); - assert(c->ref >= 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) { - assert(error >= 0); - assert(error < PA_ERR_MAX); + pa_assert(error >= 0); + pa_assert(error < PA_ERR_MAX); if (c) c->error = error; @@ -278,24 +305,32 @@ int pa_context_set_error(pa_context *c, int error) { return error; } +void pa_context_fail(pa_context *c, int error) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_set_error(c, error); + pa_context_set_state(c, PA_CONTEXT_FAILED); +} + static void pstream_die_callback(pa_pstream *p, void *userdata) { pa_context *c = userdata; - assert(p); - assert(c); - + 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; - - assert(p); - assert(packet); - assert(c); + + 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); @@ -305,23 +340,24 @@ static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_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; - - assert(p); - assert(chunk); - assert(chunk->memblock); - assert(chunk->length); - assert(c); - assert(c->ref >= 1); + + 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))) { - assert(seek == PA_SEEK_RELATIVE && offset == 0); + 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; @@ -333,49 +369,62 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o pa_context_unref(c); } -int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t) { - assert(c); - assert(c->ref >= 1); +int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t, pa_bool_t fail) { + uint32_t err; + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); if (command == PA_COMMAND_ERROR) { - assert(t); - - if (pa_tagstruct_getu32(t, &c->error) < 0) { + pa_assert(t); + + if (pa_tagstruct_getu32(t, &err) < 0) { pa_context_fail(c, PA_ERR_PROTOCOL); return -1; - } + } else if (command == PA_COMMAND_TIMEOUT) - c->error = PA_ERR_TIMEOUT; + err = PA_ERR_TIMEOUT; else { pa_context_fail(c, PA_ERR_PROTOCOL); return -1; } + if (err == PA_OK) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return -1; + } + + if (err >= PA_ERR_MAX) + err = PA_ERR_UNKNOWN; + + if (fail) { + pa_context_fail(c, err); + return -1; + } + + pa_context_set_error(c, err); + return 0; } static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_context *c = userdata; - - assert(pd); - assert(c); - assert(c->state == PA_CONTEXT_AUTHORIZING || c->state == PA_CONTEXT_SETTING_NAME); + + 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); + if (command != PA_COMMAND_REPLY) { + pa_context_handle_error(c, command, t, TRUE); goto finish; } switch(c->state) { case PA_CONTEXT_AUTHORIZING: { pa_tagstruct *reply; + pa_bool_t shm_on_remote; if (pa_tagstruct_getu32(t, &c->version) < 0 || !pa_tagstruct_eof(t)) { @@ -389,25 +438,45 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t goto finish; } + /* Starting with protocol version 13 the MSB of the version + tag reflects if shm is available for this connection or + not. */ + if (c->version >= 13) { + shm_on_remote = !!(c->version & 0x80000000U); + c->version &= 0x7FFFFFFFU; + } + + pa_log_debug("Protocol version: remote %u, local %u", c->version, PA_PROTOCOL_VERSION); + /* Enable shared memory support if possible */ - if (c->version >= 10 && - pa_mempool_is_shared(c->mempool) && - c->is_local) { + if (c->do_shm) + if (c->version < 10 || (c->version >= 13 && !shm_on_remote)) + c->do_shm = FALSE; + + if (c->do_shm) { /* Only enable SHM if both sides are owned by the same * user. This is a security measure because otherwise * data private to the user might leak. */ -#ifdef HAVE_CREDS +#ifdef HAVE_CREDS const pa_creds *creds; - if ((creds = pa_pdispatch_creds(pd))) - if (getuid() == creds->uid) - pa_pstream_use_shm(c->pstream, 1); + if (!(creds = pa_pdispatch_creds(pd)) || getuid() != creds->uid) + c->do_shm = FALSE; #endif } + pa_log_debug("Negotiated SHM: %s", pa_yes_no(c->do_shm)); + pa_pstream_enable_shm(c->pstream, c->do_shm); + reply = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag); - pa_tagstruct_puts(reply, c->name); + + if (c->version >= 13) { + pa_init_proplist(c->proplist); + pa_tagstruct_put_proplist(reply, c->proplist); + } else + pa_tagstruct_puts(reply, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)); + pa_pstream_send_tagstruct(c->pstream, reply); pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL); @@ -416,11 +485,19 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t } case PA_CONTEXT_SETTING_NAME : + + if ((c->version >= 13 && (pa_tagstruct_getu32(t, &c->client_index) < 0 || + c->client_index == PA_INVALID_INDEX)) || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + pa_context_set_state(c, PA_CONTEXT_READY); break; - + default: - assert(0); + pa_assert_not_reached(); } finish: @@ -430,27 +507,36 @@ finish: static void setup_context(pa_context *c, pa_iochannel *io) { pa_tagstruct *t; uint32_t tag; - - assert(c); - assert(io); + + pa_assert(c); + pa_assert(io); pa_context_ref(c); - - assert(!c->pstream); + + 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); - assert(!c->pdispatch); + 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."); + pa_log_info("No cookie loaded. Attempting to connect without."); t = pa_tagstruct_command(c, PA_COMMAND_AUTH, &tag); - pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION); + + c->do_shm = + pa_mempool_is_shared(c->mempool) && + c->is_local; + + pa_log_debug("SHM possible: %s", pa_yes_no(c->do_shm)); + + /* Starting with protocol version 13 we use the MSB of the version + * tag for informing the other side if we could do SHM or not */ + pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION | (c->do_shm ? 0x80000000U : 0)); pa_tagstruct_put_arbitrary(t, c->conf->cookie, sizeof(c->conf->cookie)); #ifdef HAVE_CREDS @@ -462,13 +548,13 @@ static void setup_context(pa_context *c, pa_iochannel *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); @@ -486,29 +572,32 @@ static int context_connect_spawn(pa_context *c) { int fds[2] = { -1, -1} ; pa_iochannel *io; + if (getuid() == 0) + return -1; + pa_context_ref(c); - + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { - pa_log("socketpair(): %s", pa_cstrerror(errno)); + pa_log_error("socketpair(): %s", pa_cstrerror(errno)); pa_context_fail(c, PA_ERR_INTERNAL); goto fail; } - pa_fd_set_cloexec(fds[0], 1); - - pa_socket_low_delay(fds[0]); - pa_socket_low_delay(fds[1]); + 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_log_error("fork(): %s", pa_cstrerror(errno)); pa_context_fail(c, PA_ERR_INTERNAL); if (c->spawn_api.postfork) c->spawn_api.postfork(); - + goto fail; } else if (!pid) { /* Child */ @@ -518,21 +607,25 @@ static int context_connect_spawn(pa_context *c) { #define MAX_ARGS 64 const char * argv[MAX_ARGS+1]; int n; + char *f; + + pa_close_all(fds[1], -1); + + f = pa_sprintf_malloc("%i", fds[1]); + pa_set_env("PULSE_PASSED_FD", f); + pa_xfree(f); - /* Not required, since fds[0] has CLOEXEC enabled anyway */ - close(fds[0]); - if (c->spawn_api.atfork) c->spawn_api.atfork(); /* Setup argv */ n = 0; - + argv[n++] = c->conf->daemon_binary; argv[n++] = "--daemonize=yes"; - - snprintf(t, sizeof(t), "-Lmodule-native-protocol-fd fd=%i", fds[1]); + + pa_snprintf(t, sizeof(t), "-Lmodule-native-protocol-fd fd=%i", fds[1]); argv[n++] = strdup(t); while (n < MAX_ARGS) { @@ -540,7 +633,7 @@ static int context_connect_spawn(pa_context *c) { if (!(a = pa_split_spaces(c->conf->extra_arguments, &state))) break; - + argv[n++] = a; } @@ -549,15 +642,18 @@ static int context_connect_spawn(pa_context *c) { execv(argv[0], (char * const *) argv); _exit(1); #undef MAX_ARGS - } + } /* Parent */ + pa_assert_se(pa_close(fds[1]) == 0); + fds[1] = -1; + 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); @@ -567,24 +663,19 @@ static int context_connect_spawn(pa_context *c) { goto fail; } - close(fds[1]); + c->is_local = TRUE; - c->is_local = 1; - - io = pa_iochannel_new(c->mainloop, fds[0], fds[0]); + unlock_autospawn_lock_file(c); + 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: - if (fds[0] != -1) - close(fds[0]); - if (fds[1] != -1) - close(fds[1]); + pa_close_pipe(fds); unlock_autospawn_lock_file(c); @@ -598,16 +689,16 @@ fail: static int try_next_connection(pa_context *c) { char *u = NULL; int r = -1; - - assert(c); - assert(!c->client); + + 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 @@ -616,20 +707,20 @@ static int try_next_connection(pa_context *c) { goto finish; } #endif - + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); goto finish; } - - pa_log_debug("Trying to connect to %s...", u); + + 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); + + c->is_local = !!pa_socket_client_is_local(c->client); pa_socket_client_set_callback(c->client, on_connection, c); break; } @@ -638,16 +729,17 @@ static int try_next_connection(pa_context *c) { finish: pa_xfree(u); - + return r; } static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata) { pa_context *c = userdata; - - assert(client); - assert(c); - assert(c->state == PA_CONTEXT_CONNECTING); + int saved_errno = errno; + + pa_assert(client); + pa_assert(c); + pa_assert(c->state == PA_CONTEXT_CONNECTING); pa_context_ref(c); @@ -656,7 +748,9 @@ static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userd if (!io) { /* Try the item in the list */ - if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == EHOSTUNREACH) { + if (saved_errno == ECONNREFUSED || + saved_errno == ETIMEDOUT || + saved_errno == EHOSTUNREACH) { try_next_connection(c); goto finish; } @@ -672,16 +766,39 @@ finish: pa_context_unref(c); } + +static char *get_legacy_runtime_dir(void) { + char *p, u[128]; + struct stat st; + + if (!pa_get_user_name(u, sizeof(u))) + return NULL; + + p = pa_sprintf_malloc("/tmp/pulse-%s", u); + + if (stat(p, &st) < 0) { + pa_xfree(p); + return NULL; + } + + if (st.st_uid != getuid()) { + pa_xfree(p); + return NULL; + } + + return p; +} + int pa_context_connect( pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) { - + int r = -1; - - assert(c); - assert(c->ref >= 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); @@ -692,19 +809,19 @@ int pa_context_connect( pa_context_ref(c); - assert(!c->server_list); - + 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]; + char *d, *ufn; + static char *legacy_dir; /* Prepend in reverse order */ - + if ((d = getenv("DISPLAY"))) { char *e; d = pa_xstrdup(d); @@ -716,79 +833,94 @@ int pa_context_connect( 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); + c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET); + + /* The old per-user instance path. This is supported only to ease upgrades */ + if ((legacy_dir = get_legacy_runtime_dir())) { + char *p = pa_sprintf_malloc("%s" PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET, legacy_dir); + c->server_list = pa_strlist_prepend(c->server_list, p); + pa_xfree(p); + pa_xfree(legacy_dir); + } /* The per-user instance */ - c->server_list = pa_strlist_prepend(c->server_list, pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET, ufn, sizeof(ufn))); + if ((ufn = pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET))) { + c->server_list = pa_strlist_prepend(c->server_list, ufn); + pa_xfree(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]; + char *lf; + + if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) { + pa_context_fail(c, PA_ERR_ACCESS); + goto finish; + } - pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf)); - pa_make_secure_parent_dir(lf, 0700, (uid_t)-1, (gid_t)-1); - assert(c->autospawn_lock_fd <= 0); + pa_assert(c->autospawn_lock_fd <= 0); c->autospawn_lock_fd = pa_lock_lockfile(lf); + pa_xfree(lf); if (api) c->spawn_api = *api; - c->do_autospawn = 1; - } + c->do_autospawn = TRUE; + } } 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) { - assert(c); - assert(c->ref >= 1); - - pa_context_set_state(c, PA_CONTEXT_TERMINATED); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (PA_CONTEXT_IS_GOOD(c->state)) + pa_context_set_state(c, PA_CONTEXT_TERMINATED); } pa_context_state_t pa_context_get_state(pa_context *c) { - assert(c); - assert(c->ref >= 1); - + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + return c->state; } int pa_context_errno(pa_context *c) { - assert(c); - assert(c->ref >= 1); - + 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) { - assert(c); - assert(c->ref >= 1); - + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + c->state_callback = cb; c->state_userdata = userdata; } int pa_context_is_pending(pa_context *c) { - assert(c); - assert(c->ref >= 1); + 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); + PA_CHECK_VALIDITY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE); return (c->pstream && pa_pstream_is_pending(c->pstream)) || (c->pdispatch && pa_pdispatch_is_pending(c->pdispatch)) || @@ -807,16 +939,16 @@ static void pstream_drain_callback(PA_GCC_UNUSED pa_pstream *s, void *userdata) static void set_dispatch_callbacks(pa_operation *o) { int done = 1; - - assert(o); - assert(o->ref >= 1); - assert(o->context); - assert(o->context->ref >= 1); - assert(o->context->state == PA_CONTEXT_READY); + + 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; @@ -832,7 +964,7 @@ static void set_dispatch_callbacks(pa_operation *o) { 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); } @@ -840,13 +972,13 @@ static void set_dispatch_callbacks(pa_operation *o) { pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata) { pa_operation *o; - - assert(c); - assert(c->ref >= 1); + + 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)); @@ -856,16 +988,16 @@ pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *u 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; - - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; success = 0; @@ -884,35 +1016,16 @@ finish: pa_operation_unref(o); } -pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) { +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; - assert(c); - assert(c->ref >= 1); + 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; - - assert(c); - assert(c->ref >= 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); @@ -922,18 +1035,24 @@ pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, pa return o; } +pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return pa_context_send_simple_command(c, PA_COMMAND_EXIT, pa_context_simple_ack_callback, (pa_operation_cb_t) cb, userdata); +} + pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { pa_tagstruct *t; pa_operation *o; uint32_t tag; - - assert(c); - assert(c->ref >= 1); + + 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); @@ -947,13 +1066,12 @@ pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_ pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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); + 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); @@ -963,28 +1081,39 @@ pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_ } int pa_context_is_local(pa_context *c) { - assert(c); - - return c->is_local; + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, -1); + + return !!c->is_local; } pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { - pa_tagstruct *t; pa_operation *o; - uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(name); + 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); + if (c->version >= 13) { + pa_proplist *p = pa_proplist_new(); + + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name); + o = pa_context_proplist_update(c, PA_UPDATE_REPLACE, p, cb, userdata); + pa_proplist_free(p); + } else { + pa_tagstruct *t; + uint32_t tag; + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + t = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + } return o; } @@ -994,17 +1123,17 @@ const char* pa_get_library_version(void) { } const char* pa_context_get_server(pa_context *c) { - assert(c); - assert(c->ref >= 1); + 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; } @@ -1013,8 +1142,10 @@ uint32_t pa_context_get_protocol_version(PA_GCC_UNUSED pa_context *c) { } uint32_t pa_context_get_server_protocol_version(pa_context *c) { - assert(c); - assert(c->ref >= 1); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, PA_INVALID_INDEX); return c->version; } @@ -1022,12 +1153,80 @@ uint32_t pa_context_get_server_protocol_version(pa_context *c) { pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *tag) { pa_tagstruct *t; - assert(c); - assert(tag); - + 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; } + +uint32_t pa_context_get_index(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(c, c->version >= 13, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX); + + return c->client_index; +} + +pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, pa_proplist *p, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_UPDATE_CLIENT_PROPLIST, &tag); + pa_tagstruct_putu32(t, (uint32_t) mode); + pa_tagstruct_put_proplist(t, p); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update c->proplist here, because we + * don't export that field */ + + return o; +} + +pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + const char * const *k; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, keys && keys[0], PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_CLIENT_PROPLIST, &tag); + + for (k = keys; *k; k++) + pa_tagstruct_puts(t, *k); + + pa_tagstruct_puts(t, NULL); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update c->proplist here, because we + * don't export that field */ + + return o; +} diff --git a/src/pulse/context.h b/src/pulse/context.h index 661ff617..8dff7642 100644 --- a/src/pulse/context.h +++ b/src/pulse/context.h @@ -1,21 +1,22 @@ #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 @@ -27,6 +28,7 @@ #include <pulse/mainloop-api.h> #include <pulse/cdecl.h> #include <pulse/operation.h> +#include <pulse/proplist.h> /** \page async Asynchronous API * @@ -50,7 +52,7 @@ * 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 + * 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. @@ -76,7 +78,7 @@ * 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 @@ -163,9 +165,15 @@ typedef void (*pa_context_notify_cb_t)(pa_context *c, void *userdata); 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 */ + * and an application name. It is recommended to use pa_context_new_with_proplist() + * instead and specify some initial properties.*/ pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name); +/** Instantiate a new connection context with an abstract mainloop API + * and an application name, and specify the the initial client property + * list. \since 0.9.11 */ +pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist); + /** Decrease the reference counter of the context by one */ void pa_context_unref(pa_context *c); @@ -204,27 +212,42 @@ pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *u * 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 */ +/** Set the name of the default sink. */ pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); -/** Set the name of the default source. \since 0.4 */ +/** Set the name of the default source. */ pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); -/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. \since 0.5 */ +/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. */ int pa_context_is_local(pa_context *c); -/** Set a different application name for context on the server. \since 0.5 */ +/** Set a different application name for context on the server. */ pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); -/** Return the server name this context is connected to. \since 0.7 */ +/** Return the server name this context is connected to. */ const char* pa_context_get_server(pa_context *c); -/** Return the protocol version of the library. \since 0.8 */ +/** Return the protocol version of the library. */ uint32_t pa_context_get_protocol_version(pa_context *c); -/** Return the protocol version of the connected server. \since 0.8 */ +/** Return the protocol version of the connected server. */ uint32_t pa_context_get_server_protocol_version(pa_context *c); +/* Update the property list of the client, adding new entries. Please + * note that it is highly recommended to set as much properties + * initially via pa_context_new_with_proplist() as possible instead a + * posteriori with this function, since that information may then be + * used to route streams of the client to the right device. \since 0.9.11 */ +pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, pa_proplist *p, pa_context_success_cb_t cb, void *userdata); + +/* Update the property list of the client, remove entries. \since 0.9.11 */ +pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata); + +/** Return the client index this context is + * identified in the server with. This is useful for usage with the + * introspection functions, such as pa_context_get_client_info(). \since 0.9.11 */ +uint32_t pa_context_get_index(pa_context *s); + PA_C_DECL_END #endif diff --git a/src/pulse/def.h b/src/pulse/def.h index a22e3c19..a91c1031 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -1,21 +1,22 @@ #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 @@ -45,6 +46,15 @@ typedef enum pa_context_state { PA_CONTEXT_TERMINATED /**< The connection was terminated cleanly */ } pa_context_state_t; +/** Return non-zero if the passed state is one of the connected states */ +static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) { + return + x == PA_CONTEXT_CONNECTING || + x == PA_CONTEXT_AUTHORIZING || + x == PA_CONTEXT_SETTING_NAME || + x == PA_CONTEXT_READY; +} + /** The state of a stream */ typedef enum pa_stream_state { PA_STREAM_UNCONNECTED, /**< The stream is not yet connected to any sink or source */ @@ -54,6 +64,13 @@ typedef enum pa_stream_state { PA_STREAM_TERMINATED /**< The stream has been terminated cleanly */ } pa_stream_state_t; +/** Return non-zero if the passed state is one of the connected states */ +static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) { + return + x == PA_STREAM_CREATING || + x == PA_STREAM_READY; +} + /** The state of an operation */ typedef enum pa_operation_state { PA_OPERATION_RUNNING, /**< The operation is still running */ @@ -64,12 +81,12 @@ typedef enum pa_operation_state { /** An invalid index */ #define PA_INVALID_INDEX ((uint32_t) -1) -/** Some special flags for contexts. \since 0.8 */ +/** Some special flags for contexts. */ 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 */ +/** The direction of a pa_stream object */ typedef enum pa_stream_direction { PA_STREAM_NODIRECTION, /**< Invalid direction */ PA_STREAM_PLAYBACK, /**< Playback stream */ @@ -77,7 +94,7 @@ typedef enum pa_stream_direction { PA_STREAM_UPLOAD /**< Sample upload stream */ } pa_stream_direction_t; -/** Some special flags for stream connections. \since 0.6 */ +/** Some special flags for stream connections. */ 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 @@ -119,7 +136,7 @@ typedef enum pa_stream_flags { * ahead can be corrected * quickly, without the need to * wait. */ - PA_STREAM_AUTO_TIMING_UPDATE = 8 /**< If set timing update requests + PA_STREAM_AUTO_TIMING_UPDATE = 8, /**< If set timing update requests * are issued periodically * automatically. Combined with * PA_STREAM_INTERPOLATE_TIMING @@ -129,15 +146,150 @@ typedef enum pa_stream_flags { * 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_PEAK_DETECT = 2048, /**< Find peaks instead of + * resampling. \since 0.9.11 */ + + PA_STREAM_START_MUTED = 4096, /**< Create in muted state. \since 0.9.11 */ + + + PA_STREAM_ADJUST_LATENCY = 8192, /**< Try to adjust the latency of + * the sink/source based on the + * requested buffer metrics and + * adjust buffer metrics + * accordingly. \since 0.9.11 */ } pa_stream_flags_t; + +/** English is an evil language \since 0.9.11 */ +#define PA_STREAM_NOT_MONOTONIC PA_STREAM_NOT_MONOTONOUS + /** 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. */ + uint32_t maxlength; /**< Maximum length of the + * buffer. Setting this to 0 will + * initialize this to the maximum value + * supported by server, which is + * recommended. */ + 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. It is + * recommended to set this to 0, which + * will initialize this to a value that + * is deemed sensible by the + * server. However, this value will + * default to something like 2s, i.e. for + * applications that have specific + * latency requirements this value should + * be set to the maximum latency that the + * application can deal with. */ + uint32_t prebuf; /**< Playback only: pre-buffering. The + * server does not start with playback + * before at least prebug bytes are + * available in the buffer. It is + * recommended to set this to 0, which + * will initialize this to the same value + * as tlength, whatever that may be. */ + uint32_t minreq; /**< Playback only: minimum request. The + * server does not request less than + * minreq bytes from the client, instead + * waits until the buffer is free enough + * to request more bytes at once. It is + * recommended to set this to 0, which + * will initialize this to a value that + * is deemed sensible by the server. */ + 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. It is + * recommended to set this to 0, which + * will initialize this to a value that + * is deemed sensible by the + * server. However, this value will + * default to something like 2s, i.e. for + * applications that have specific + * latency requirements this value should + * be set to the maximum latency that the + * application can deal with. */ } pa_buffer_attr; /** Error values as used by pa_context_errno(). Use pa_strerror() to convert these values to human readable strings */ @@ -149,7 +301,7 @@ enum { PA_ERR_EXIST, /**< Entity exists */ PA_ERR_NOENTITY, /**< No such entity */ PA_ERR_CONNECTIONREFUSED, /**< Connection refused */ - PA_ERR_PROTOCOL, /**< Protocol error */ + PA_ERR_PROTOCOL, /**< Protocol error */ PA_ERR_TIMEOUT, /**< Timeout */ PA_ERR_AUTHKEY, /**< No authorization key */ PA_ERR_INTERNAL, /**< Internal error */ @@ -159,9 +311,10 @@ enum { 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_VERSION, /**< Incompatible protocol version */ + PA_ERR_TOOLARGE, /**< Data too large */ PA_ERR_NOTSUPPORTED, /**< Operation not supported \since 0.9.5 */ + PA_ERR_UNKNOWN, /**< The error code was unknown to the client */ PA_ERR_MAX /**< Not really an error but the first invalid error code */ }; @@ -175,9 +328,9 @@ typedef enum pa_subscription_mask { 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_SERVER = 128, /**< Other global server changes. */ + PA_SUBSCRIPTION_MASK_AUTOLOAD = 256, /**< Autoload table events. */ + PA_SUBSCRIPTION_MASK_ALL = 511 /**< Catch all events */ } pa_subscription_mask_t; /** Subscription event types, as used by pa_context_subscribe() */ @@ -189,8 +342,8 @@ typedef enum pa_subscription_event_type { 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_SERVER = 7, /**< Event type: Global server change, only occuring with PA_SUBSCRIPTION_EVENT_CHANGE. */ + PA_SUBSCRIPTION_EVENT_AUTOLOAD = 8, /**< Event type: Autoload table changes. */ 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 */ @@ -207,7 +360,7 @@ typedef enum pa_subscription_event_type { * 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 + * 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 @@ -217,7 +370,9 @@ typedef enum pa_subscription_event_type { * 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().*/ + * described here are implemented in pa_stream_get_latency(). Please + * note that this structure can be extended as part of evolutionary + * API updates at any time in any new release.*/ typedef struct pa_timing_info { struct timeval timestamp; /**< The time when this timing info structure was current */ int synchronized_clocks; /**< Non-zero if the local and the @@ -226,14 +381,21 @@ typedef struct pa_timing_info { * detected transport_usec becomes much * more reliable. However, the code that * detects synchronized clocks is very - * limited und unreliable itself. \since - * 0.5 */ + * limited und unreliable itself. */ pa_usec_t sink_usec; /**< Time in usecs a sample takes to be played on the sink. For playback streams and record streams connected to a monitor source. */ - pa_usec_t source_usec; /**< Time in usecs a sample takes from being recorded to being delivered to the application. Only for record streams. \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 */ + pa_usec_t source_usec; /**< Time in usecs a sample takes from being recorded to being delivered to the application. Only for record streams. */ + pa_usec_t transport_usec; /**< Estimated time in usecs a sample takes to be transferred to/from the daemon. For both playback and record streams. */ - int playing; /**< Non-zero when the stream is currently playing. Only for playback streams. */ + int playing; /**< Non-zero when the stream is + * currently not underrun and data is + * being passed on to the device. Only + * for playback streams. This field does + * not say whether the data is actually + * already being played. To determine + * this check whether since_underrun + * (converted to usec) is larger than + * sink_usec.*/ int write_index_corrupt; /**< Non-zero if write_index is not * up-to-date because a local write @@ -242,28 +404,40 @@ typedef struct pa_timing_info { * info was current . Only write * commands with SEEK_RELATIVE_ON_READ * and SEEK_RELATIVE_END can corrupt - * write_index. \since 0.8 */ + * write_index. */ int64_t write_index; /**< Current write index into the * playback buffer in bytes. Think twice before * using this for seeking purposes: it * might be out of date a the time you * want to use it. Consider using - * PA_SEEK_RELATIVE instead. \since - * 0.8 */ + * PA_SEEK_RELATIVE instead. */ int read_index_corrupt; /**< Non-zero if read_index is not * up-to-date because a local pause or * flush request that corrupted it has * been issued in the time since this - * latency info was current. \since 0.8 */ - + * latency info was current. */ + int64_t read_index; /**< Current read index into the * playback buffer in bytes. Think twice before * using this for seeking purposes: it * might be out of date a the time you * want to use it. Consider using * PA_SEEK_RELATIVE_ON_READ - * instead. \since 0.8 */ + * instead. */ + + pa_usec_t configured_sink_usec; /**< The static configured latency for + * the sink. \since 0.9.11 */ + pa_usec_t configured_source_usec; /**< The static configured latency for + * the source. \since 0.9.11 */ + + int64_t since_underrun; /**< Bytes that were handed to the sink + since the last underrun happened, or + since playback started again after + the last underrun. playing will tell + you which case it is. \since + 0.9.11 */ + } pa_timing_info; /** A structure for the spawn api. This may be used to integrate auto @@ -272,7 +446,7 @@ typedef struct pa_timing_info { * 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 */ + * prefork/postfork. */ typedef struct pa_spawn_api { void (*prefork)(void); /**< Is called just before the fork in the parent process. May be NULL. */ void (*postfork)(void); /**< Is called immediately after the fork in the parent process. May be NULL.*/ @@ -285,26 +459,32 @@ typedef struct pa_spawn_api { * passed to the new process. */ } pa_spawn_api; -/** Seek type for pa_stream_write(). \since 0.8*/ +/** Seek type for pa_stream_write(). */ 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_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 */ +/** Special sink flags. */ 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_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_HW_MUTE_CTRL = 16, /**< Supports hardware mute control \since 0.9.11 */ + PA_SINK_DECIBEL_VOLUME = 32 /**< Volume can be translated to dB with pa_sw_volume_to_dB() \since 0.9.11 */ } pa_sink_flags_t; -/** Special source flags. \since 0.8 */ +/** Special source flags. */ 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_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_HW_MUTE_CTRL = 16, /**< Supports hardware mute control \since 0.9.11 */ + PA_SOURCE_DECIBEL_VOLUME = 32 /**< Volume can be translated to dB with pa_sw_volume_to_dB() \since 0.9.11 */ } pa_source_flags_t; /** A generic free() like callback prototype */ diff --git a/src/pulse/error.c b/src/pulse/error.c index 7bd31ead..29690c89 100644 --- a/src/pulse/error.c +++ b/src/pulse/error.c @@ -1,18 +1,19 @@ -/* $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 diff --git a/src/pulse/error.h b/src/pulse/error.h index bfce023c..9f9e3d33 100644 --- a/src/pulse/error.h +++ b/src/pulse/error.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulsecore/gccmacro.h b/src/pulse/gccmacro.h index 8825700a..e4062033 100644 --- a/src/pulsecore/gccmacro.h +++ b/src/pulse/gccmacro.h @@ -1,21 +1,21 @@ #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 @@ -50,4 +50,47 @@ #define PA_GCC_UNUSED #endif +#ifdef __GNUC__ +#define PA_GCC_DESTRUCTOR __attribute__ ((destructor)) +#else +/** Call this function when process terminates */ +#define PA_GCC_DESTRUCTOR +#endif + +#ifndef PA_GCC_PURE +#ifdef __GNUC__ +#define PA_GCC_PURE __attribute__ ((pure)) +#else +/** This function's return value depends only the arguments list and global state **/ +#define PA_GCC_PURE +#endif +#endif + +#ifndef PA_GCC_CONST +#ifdef __GNUC__ +#define PA_GCC_CONST __attribute__ ((const)) +#else +/** This function's return value depends only the arguments list (stricter version of PA_GCC_PURE) **/ +#define PA_GCC_CONST +#endif +#endif + +#ifndef PA_GCC_DEPRECATED +#ifdef __GNUC__ +#define PA_GCC_DEPRECATED __attribute__ ((deprecated)) +#else +/** This function is deprecated **/ +#define PA_GCC_DEPRECATED +#endif +#endif + +#ifndef PA_GCC_PACKED +#ifdef __GNUCC__ +#define PA_GCC_PACKED __attribute__ ((packed)) +#else +/** Structure shall be packed in memory **/ +#define PA_GCC_PACKED +#endif +#endif + #endif diff --git a/src/pulse/glib-mainloop.c b/src/pulse/glib-mainloop.c index 201b6e23..6ddb0faa 100644 --- a/src/pulse/glib-mainloop.c +++ b/src/pulse/glib-mainloop.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,8 +23,6 @@ #include <config.h> #endif -#include <assert.h> - #include <pulse/xmalloc.h> #include <pulse/timeval.h> @@ -69,7 +67,7 @@ struct pa_defer_event { int dead; int enabled; - + pa_defer_event_cb_t callback; void *userdata; pa_defer_event_destroy_cb_t destroy_callback; @@ -79,7 +77,7 @@ struct pa_defer_event { struct pa_glib_mainloop { GSource source; - + pa_mainloop_api api; GMainContext *context; @@ -102,7 +100,7 @@ static void cleanup_io_events(pa_glib_mainloop *g, int force) { if (!force && g->io_events_please_scan <= 0) break; - + if (force || e->dead) { PA_LLIST_REMOVE(pa_io_event, g->io_events, e); @@ -110,13 +108,13 @@ static void cleanup_io_events(pa_glib_mainloop *g, int force) { 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); } @@ -135,7 +133,7 @@ static void cleanup_time_events(pa_glib_mainloop *g, int force) { if (!force && g->time_events_please_scan <= 0) break; - + if (force || e->dead) { PA_LLIST_REMOVE(pa_time_event, g->time_events, e); @@ -148,10 +146,10 @@ static void cleanup_time_events(pa_glib_mainloop *g, int force) { 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); } @@ -170,7 +168,7 @@ static void cleanup_defer_events(pa_glib_mainloop *g, int force) { if (!force && g->defer_events_please_scan <= 0) break; - + if (force || e->dead) { PA_LLIST_REMOVE(pa_defer_event, g->defer_events, e); @@ -183,10 +181,10 @@ static void cleanup_defer_events(pa_glib_mainloop *g, int force) { 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); } @@ -218,7 +216,7 @@ static pa_io_event* glib_io_new( pa_io_event_flags_t f, pa_io_event_cb_t cb, void *userdata) { - + pa_io_event *e; pa_glib_mainloop *g; @@ -226,7 +224,7 @@ static pa_io_event* glib_io_new( g_assert(m->userdata); g_assert(fd >= 0); g_assert(cb); - + g = m->userdata; e = pa_xnew(pa_io_event, 1); @@ -236,7 +234,7 @@ static pa_io_event* glib_io_new( 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; @@ -245,7 +243,7 @@ static pa_io_event* glib_io_new( g_source_add_poll(&g->source, &e->poll_fd); e->poll_fd_added = 1; - + return e; } @@ -272,7 +270,7 @@ static void glib_io_free(pa_io_event*e) { 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; } @@ -283,14 +281,14 @@ static pa_time_event* glib_time_new( 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); @@ -308,13 +306,13 @@ static pa_time_event* glib_time_new( 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; } @@ -328,12 +326,12 @@ static void glib_time_restart(pa_time_event*e, const struct timeval *tv) { } else if (!e->enabled && tv) e->mainloop->n_enabled_time_events++; - if ((e->enabled = !!tv)) + 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) @@ -357,7 +355,7 @@ static void glib_time_free(pa_time_event *e) { 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; } @@ -367,27 +365,27 @@ 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; } @@ -430,7 +428,7 @@ static void glib_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_ static void glib_quit(pa_mainloop_api*a, PA_GCC_UNUSED int retval) { g_warning("quit() ignored"); - + /* NOOP */ } @@ -440,7 +438,7 @@ static pa_time_event* find_next_time_event(pa_glib_mainloop *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) @@ -461,7 +459,7 @@ static pa_time_event* find_next_time_event(pa_glib_mainloop *g) { static void scan_dead(pa_glib_mainloop *g) { g_assert(g); - + if (g->io_events_please_scan) cleanup_io_events(g, 0); @@ -499,7 +497,7 @@ static gboolean prepare_func(GSource *source, gint *timeout) { if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { *timeout = 0; return TRUE; - } + } usec = pa_timeval_diff(&t->timeval, &tvnow); *timeout = (gint) (usec / 1000); } else @@ -519,10 +517,10 @@ static gboolean check_func(GSource *source) { 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; @@ -555,7 +553,7 @@ static gboolean dispatch_func(GSource *source, PA_GCC_UNUSED GSourceFunc callbac } g_assert(d); - + d->callback(&g->api, d, d->userdata); return TRUE; } @@ -567,7 +565,7 @@ static gboolean dispatch_func(GSource *source, PA_GCC_UNUSED GSourceFunc callbac 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; @@ -576,7 +574,7 @@ static gboolean dispatch_func(GSource *source, PA_GCC_UNUSED GSourceFunc callbac /* Disable time event */ glib_time_restart(t, NULL); - + t->callback(&g->api, t, &t->timeval, t->userdata); return TRUE; } @@ -604,12 +602,12 @@ static const pa_mainloop_api vtable = { .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, }; @@ -624,10 +622,10 @@ pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) { 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; @@ -639,10 +637,10 @@ pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) { 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; } @@ -660,6 +658,6 @@ void pa_glib_mainloop_free(pa_glib_mainloop* g) { 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 index af7cc0e9..60fd61a3 100644 --- a/src/pulse/glib-mainloop.h +++ b/src/pulse/glib-mainloop.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 76d80d83..9ed541d1 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -1,21 +1,22 @@ #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 @@ -38,15 +39,17 @@ #include <pulsecore/mcalign.h> #include <pulsecore/memblockq.h> #include <pulsecore/hashmap.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/time-smoother.h> #include "client-conf.h" #define DEFAULT_TIMEOUT (30) struct pa_context { - int ref; - - char *name; + PA_REFCNT_DECLARE; + + pa_proplist *proplist; pa_mainloop_api* mainloop; pa_socket_client *client; @@ -65,66 +68,80 @@ struct pa_context { 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; + pa_bool_t is_local:1; + pa_bool_t do_autospawn:1; + pa_bool_t do_shm:1; int autospawn_lock_fd; pa_spawn_api spawn_api; - + pa_strlist *server_list; char *server; pa_client_conf *conf; + + uint32_t client_index; }; -#define PA_MAX_WRITE_INDEX_CORRECTIONS 10 +#define PA_MAX_WRITE_INDEX_CORRECTIONS 32 typedef struct pa_index_correction { uint32_t tag; - int valid; int64_t value; - int absolute, corrupt; + pa_bool_t valid:1; + pa_bool_t absolute:1; + pa_bool_t corrupt:1; } pa_index_correction; struct pa_stream { - int ref; + PA_REFCNT_DECLARE; + PA_LLIST_FIELDS(pa_stream); + pa_context *context; pa_mainloop_api *mainloop; - PA_LLIST_FIELDS(pa_stream); - char *name; - pa_buffer_attr buffer_attr; + uint32_t direct_on_input; + + pa_stream_direction_t direction; + pa_stream_state_t state; + pa_stream_flags_t flags; + pa_sample_spec sample_spec; pa_channel_map channel_map; - pa_stream_flags_t flags; + + pa_proplist *proplist; + + pa_bool_t channel_valid:1; + pa_bool_t suspended:1; + pa_bool_t corked:1; + pa_bool_t timing_info_valid:1; + pa_bool_t auto_timing_update_requested:1; + uint32_t channel; uint32_t syncid; - int channel_valid; - uint32_t device_index; - pa_stream_direction_t direction; - pa_stream_state_t state; - + uint32_t stream_index; + uint32_t requested_bytes; + pa_buffer_attr buffer_attr; + + uint32_t device_index; + char *device_name; 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; @@ -135,10 +152,8 @@ struct pa_stream { /* Latency interpolation stuff */ pa_time_event *auto_timing_update_event; - int auto_timing_update_requested; - - pa_usec_t cached_time; - int cached_time_valid; + + pa_smoother *smoother; /* Callbacks */ pa_stream_notify_cb_t state_callback; @@ -153,27 +168,38 @@ struct pa_stream { void *underflow_userdata; pa_stream_notify_cb_t latency_update_callback; void *latency_update_userdata; + pa_stream_notify_cb_t moved_callback; + void *moved_userdata; + pa_stream_notify_cb_t suspended_callback; + void *suspended_userdata; + pa_stream_notify_cb_t started_callback; + void *started_userdata; }; typedef void (*pa_operation_cb_t)(void); struct pa_operation { - int ref; + 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); +void pa_command_stream_started(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); @@ -185,7 +211,7 @@ void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t 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); +int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t, pa_bool_t fail); pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata); void pa_stream_set_state(pa_stream *s, pa_stream_state_t st); @@ -207,5 +233,4 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta #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 index b926e4e4..4be2c62a 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,11 +24,10 @@ #include <config.h> #endif -#include <assert.h> - #include <pulse/context.h> +#include <pulse/gccmacro.h> -#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> #include <pulsecore/pstream-util.h> #include "internal.h" @@ -39,16 +39,18 @@ 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; - - assert(pd); - assert(o); - assert(o->ref >= 1); + + 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; p = NULL; @@ -56,8 +58,7 @@ static void context_stat_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNU pa_tagstruct_getu32(t, &i.memblock_total_size) < 0 || pa_tagstruct_getu32(t, &i.memblock_allocated) < 0 || pa_tagstruct_getu32(t, &i.memblock_allocated_size) < 0 || - pa_tagstruct_getu32(t, &i.scache_size) < 0 || - !pa_tagstruct_eof(t)) { + pa_tagstruct_getu32(t, &i.scache_size) < 0) { pa_context_fail(o->context, PA_ERR_PROTOCOL); goto finish; } @@ -81,16 +82,18 @@ pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdat 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; - - assert(pd); - assert(o); - assert(o->ref >= 1); - + + 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; p = NULL; @@ -107,7 +110,7 @@ static void context_get_server_info_callback(pa_pdispatch *pd, uint32_t command, 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); @@ -127,25 +130,29 @@ pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, 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; - - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { uint32_t flags; - + while (!pa_tagstruct_eof(t)) { pa_sink_info i; - + pa_bool_t mute = FALSE; + + memset(&i, 0, sizeof(i)); + i.proplist = pa_proplist_new(); + if (pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_gets(t, &i.name) < 0 || pa_tagstruct_gets(t, &i.description) < 0 || @@ -153,26 +160,33 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, P 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_get_boolean(t, &mute) < 0 || pa_tagstruct_getu32(t, &i.monitor_source) < 0 || pa_tagstruct_gets(t, &i.monitor_source_name) < 0 || pa_tagstruct_get_usec(t, &i.latency) < 0 || pa_tagstruct_gets(t, &i.driver) < 0 || - pa_tagstruct_getu32(t, &flags) < 0) { - + pa_tagstruct_getu32(t, &flags) < 0 || + (o->context->version >= 13 && + (pa_tagstruct_get_proplist(t, i.proplist) < 0 || + pa_tagstruct_get_usec(t, &i.configured_latency) < 0))) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); goto finish; } + i.mute = (int) mute; 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); } + + pa_proplist_free(i.proplist); } } - + if (o->callback) { pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback; cb(o->context, NULL, eol, o->userdata); @@ -191,10 +205,10 @@ pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx, pa_ pa_tagstruct *t; pa_operation *o; uint32_t tag; - - assert(c); - assert(c->ref >= 1); - assert(cb); + + 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); @@ -213,10 +227,10 @@ pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_tagstruct *t; pa_operation *o; uint32_t tag; - - assert(c); - assert(c->ref >= 1); - assert(cb); + + 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); @@ -238,24 +252,28 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, pa_operation *o = userdata; int eol = 1; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { - + while (!pa_tagstruct_eof(t)) { pa_source_info i; uint32_t flags; - + pa_bool_t mute = FALSE; + + memset(&i, 0, sizeof(i)); + i.proplist = pa_proplist_new(); + if (pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_gets(t, &i.name) < 0 || pa_tagstruct_gets(t, &i.description) < 0 || @@ -263,26 +281,33 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, 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_get_boolean(t, &mute) < 0 || pa_tagstruct_getu32(t, &i.monitor_of_sink) < 0 || pa_tagstruct_gets(t, &i.monitor_of_sink_name) < 0 || pa_tagstruct_get_usec(t, &i.latency) < 0 || pa_tagstruct_gets(t, &i.driver) < 0 || - pa_tagstruct_getu32(t, &flags) < 0) { - + pa_tagstruct_getu32(t, &flags) < 0 || + (o->context->version >= 13 && + (pa_tagstruct_get_proplist(t, i.proplist) < 0 || + pa_tagstruct_get_usec(t, &i.configured_latency) < 0))) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); goto finish; } + i.mute = (int) mute; 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); } + + pa_proplist_free(i.proplist); } } - + if (o->callback) { pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback; cb(o->context, NULL, eol, o->userdata); @@ -302,14 +327,14 @@ pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t idx, p pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -324,9 +349,9 @@ pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -348,28 +373,34 @@ static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command, pa_operation *o = userdata; int eol = 1; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { - + while (!pa_tagstruct_eof(t)) { pa_client_info i; - + + memset(&i, 0, sizeof(i)); + i.proplist = pa_proplist_new(); + if (pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_gets(t, &i.name) < 0 || pa_tagstruct_getu32(t, &i.owner_module) < 0 || - pa_tagstruct_gets(t, &i.driver) < 0 ) { + pa_tagstruct_gets(t, &i.driver) < 0 || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); goto finish; } @@ -377,9 +408,11 @@ static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command, pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback; cb(o->context, &i, 0, o->userdata); } + + pa_proplist_free(i.proplist); } } - + if (o->callback) { pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback; cb(o->context, NULL, eol, o->userdata); @@ -395,9 +428,9 @@ pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_ pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -422,39 +455,43 @@ static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command, pa_operation *o = userdata; int eol = 1; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { - + while (!pa_tagstruct_eof(t)) { pa_module_info i; - + pa_bool_t auto_unload = FALSE; + 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_tagstruct_get_boolean(t, &auto_unload) < 0) { pa_context_fail(o->context, PA_ERR_PROTOCOL); goto finish; } + i.auto_unload = (int) auto_unload; + if (o->callback) { pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback; cb(o->context, &i, 0, o->userdata); } } } - + if (o->callback) { pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback; cb(o->context, NULL, eol, o->userdata); @@ -470,13 +507,13 @@ pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_ pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -497,23 +534,27 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm pa_operation *o = userdata; int eol = 1; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { - + while (!pa_tagstruct_eof(t)) { pa_sink_input_info i; - + pa_bool_t mute = FALSE; + + memset(&i, 0, sizeof(i)); + i.proplist = pa_proplist_new(); + if (pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_gets(t, &i.name) < 0 || pa_tagstruct_getu32(t, &i.owner_module) < 0 || @@ -525,19 +566,26 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm 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) { - + pa_tagstruct_gets(t, &i.driver) < 0 || + (o->context->version >= 11 && pa_tagstruct_get_boolean(t, &mute) < 0) || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); goto finish; } + i.mute = (int) mute; + if (o->callback) { pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback; cb(o->context, &i, 0, o->userdata); } + + pa_proplist_free(i.proplist); } } - + 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); @@ -553,13 +601,13 @@ pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sin pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -580,23 +628,26 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c pa_operation *o = userdata; int eol = 1; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { - + while (!pa_tagstruct_eof(t)) { pa_source_output_info i; - + + memset(&i, 0, sizeof(i)); + i.proplist = pa_proplist_new(); + if (pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_gets(t, &i.name) < 0 || pa_tagstruct_getu32(t, &i.owner_module) < 0 || @@ -607,9 +658,11 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c 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_tagstruct_gets(t, &i.driver) < 0 || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); goto finish; } @@ -617,9 +670,11 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback; cb(o->context, &i, 0, o->userdata); } + + pa_proplist_free(i.proplist); } } - + 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); @@ -635,13 +690,13 @@ pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_ pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -663,9 +718,9 @@ pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, c pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(volume); + 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); @@ -687,15 +742,15 @@ pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(name); - assert(volume); + 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); @@ -713,8 +768,8 @@ pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); @@ -735,13 +790,13 @@ pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(name); + 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); @@ -759,14 +814,14 @@ pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, cons pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(volume); + 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); @@ -778,14 +833,37 @@ pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, cons 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; - assert(c); - assert(c->ref >= 1); - assert(volume); + 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); @@ -807,15 +885,15 @@ pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *na pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(name); - assert(volume); + 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); @@ -833,8 +911,8 @@ pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, i pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); @@ -855,13 +933,13 @@ pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(name); + 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); @@ -880,23 +958,27 @@ static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, pa_operation *o = userdata; int eol = 1; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { - + while (!pa_tagstruct_eof(t)) { pa_sample_info i; - + pa_bool_t lazy = FALSE; + + memset(&i, 0, sizeof(i)); + i.proplist = pa_proplist_new(); + if (pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_gets(t, &i.name) < 0 || pa_tagstruct_get_cvolume(t, &i.volume) < 0 || @@ -904,20 +986,25 @@ static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, 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_tagstruct_get_boolean(t, &lazy) < 0 || + pa_tagstruct_gets(t, &i.filename) < 0 || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); goto finish; } + i.lazy = (int) lazy; + if (o->callback) { pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback; cb(o->context, &i, 0, o->userdata); } + + pa_proplist_free(i.proplist); } } - + if (o->callback) { pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback; cb(o->context, NULL, eol, o->userdata); @@ -932,10 +1019,10 @@ pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name pa_tagstruct *t; pa_operation *o; uint32_t tag; - - assert(c); - assert(c->ref >= 1); - assert(cb); + + 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); @@ -956,9 +1043,9 @@ pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, p pa_operation *o; uint32_t tag; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -983,8 +1070,8 @@ static pa_operation* command_kill(pa_context *c, uint32_t command, uint32_t idx, pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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); @@ -1002,7 +1089,7 @@ static pa_operation* command_kill(pa_context *c, uint32_t command, uint32_t idx, 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); } @@ -1015,15 +1102,15 @@ static void context_index_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UN pa_operation *o = userdata; uint32_t idx; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; idx = PA_INVALID_INDEX; @@ -1048,9 +1135,9 @@ pa_operation* pa_context_load_module(pa_context *c, const char*name, const char pa_operation *o; pa_tagstruct *t; uint32_t tag; - - assert(c); - assert(c->ref >= 1); + + 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); @@ -1076,23 +1163,25 @@ static void context_get_autoload_info_callback(pa_pdispatch *pd, uint32_t comman pa_operation *o = userdata; int eol = 1; - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 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 || @@ -1108,7 +1197,7 @@ static void context_get_autoload_info_callback(pa_pdispatch *pd, uint32_t comman } } } - + if (o->callback) { pa_autoload_info_cb_t cb = (pa_autoload_info_cb_t) o->callback; cb(o->context, NULL, eol, o->userdata); @@ -1119,14 +1208,16 @@ finish: pa_operation_unref(o); } +PA_WARN_REFERENCE(pa_context_get_autoload_info_by_name, "Autoload will no longer be implemented by future versions of the PulseAudio server."); + 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; - assert(c); - assert(c->ref >= 1); - assert(cb); + 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); @@ -1143,14 +1234,16 @@ pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *na return o; } +PA_WARN_REFERENCE(pa_context_get_autoload_info_by_index, "Autoload will no longer be implemented by future versions of the PulseAudio server."); + 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; - - assert(c); - assert(c->ref >= 1); - assert(cb); + + 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); @@ -1165,18 +1258,23 @@ pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, return o; } + +PA_WARN_REFERENCE(pa_context_get_autoload_info_list, "Autoload will no longer be implemented by future versions of the PulseAudio server."); + 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_WARN_REFERENCE(pa_context_add_autoload, "Autoload will no longer be implemented by future versions of the PulseAudio server."); + 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; - assert(c); - assert(c->ref >= 1); - + 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); @@ -1195,14 +1293,16 @@ pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autolo return o; } +PA_WARN_REFERENCE(pa_context_remove_autoload_by_name, "Autoload will no longer be implemented by future versions of the PulseAudio server."); + 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; - assert(c); - assert(c->ref >= 1); - + 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); @@ -1218,13 +1318,15 @@ pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name return o; } +PA_WARN_REFERENCE(pa_context_remove_autoload_by_index, "Autoload will no longer be implemented by future versions of the PulseAudio server."); + 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; - assert(c); - assert(c->ref >= 1); + 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); @@ -1239,13 +1341,13 @@ pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, p 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* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, const char *sink_name, pa_context_success_cb_t cb, void* userdata) { pa_operation *o; pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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); @@ -1269,8 +1371,8 @@ pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, u pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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); @@ -1289,13 +1391,13 @@ pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, u 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* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, const char *source_name, pa_context_success_cb_t cb, void* userdata) { pa_operation *o; pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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); @@ -1319,8 +1421,8 @@ pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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); @@ -1338,3 +1440,97 @@ pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx return o; } + +pa_operation* pa_context_suspend_sink_by_name(pa_context *c, const char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, 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, const char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, 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 index 28d22cd7..c8c13a75 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -1,21 +1,22 @@ #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 @@ -27,8 +28,10 @@ #include <pulse/operation.h> #include <pulse/context.h> #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> #include <pulse/channelmap.h> #include <pulse/volume.h> +#include <pulse/proplist.h> /** \page introspect Server Query and Control * @@ -203,21 +206,32 @@ PA_C_DECL_BEGIN -/** Stores information about sinks */ +#define PA_PORT_DIGITAL "spdif" +#define PA_PORT_ANALOG_STEREO "analog-stereo" +#define PA_PORT_ANALOG_5_1 "analog-5-1" +#define PA_PORT_ANALOG_4_0 "analog-4-0" + +/** @{ \name Sinks */ + +/** Stores information about sinks. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_sink_info { const char *name; /**< Name of the sink */ - uint32_t index; /**< Index of the sink */ + 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 */ + pa_channel_map channel_map; /**< Channel map */ uint32_t owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX */ pa_cvolume volume; /**< Volume of the sink */ - int mute; /**< Mute switch of the sink \since 0.8 */ + int mute; /**< Mute switch of the sink */ uint32_t monitor_source; /**< Index of the monitor source connected to this sink */ const char *monitor_source_name; /**< The name of the monitor source */ - pa_usec_t latency; /**< Length of filled playback buffer of this sink */ - const char *driver; /**< Driver name. \since 0.8 */ - pa_sink_flags_t flags; /**< Flags \since 0.8 */ + pa_usec_t latency; /**< Length of queued audio in the output buffer. */ + const char *driver; /**< Driver name. */ + pa_sink_flags_t flags; /**< Flags */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ + pa_usec_t configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */ } pa_sink_info; /** Callback prototype for pa_context_get_sink_info_by_name() and friends */ @@ -232,21 +246,47 @@ pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t id, pa_s /** 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 */ +/** Set the volume of a sink device specified by its index */ +pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a sink device specified by its name */ +pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink device specified by its index */ +pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink device specified by its name */ +pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Suspend/Resume a sink. \since 0.9.7 */ +pa_operation* pa_context_suspend_sink_by_name(pa_context *c, const char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a sink. If idx is PA_INVALID_INDEX all sinks will be suspended. \since 0.9.7 */ +pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** @} */ + +/** @{ \name Sources */ + +/** Stores information about sources. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_source_info { + const char *name; /**< Name of the source */ uint32_t index; /**< Index of the source */ const char *description; /**< Description of this source */ pa_sample_spec sample_spec; /**< Sample spec of this source */ - pa_channel_map channel_map; /**< Channel map \since 0.8 */ + pa_channel_map channel_map; /**< Channel map */ uint32_t owner_module; /**< Owning module index, or PA_INVALID_INDEX */ - pa_cvolume volume; /**< Volume of the source \since 0.8 */ - int mute; /**< Mute switch of the sink \since 0.8 */ + pa_cvolume volume; /**< Volume of the source */ + int mute; /**< Mute switch of the sink */ uint32_t monitor_of_sink; /**< If this is a monitor source the index of the owning sink, otherwise PA_INVALID_INDEX */ const char *monitor_of_sink_name; /**< Name of the owning sink, or 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_usec_t latency; /**< Length of filled record buffer of this source. */ + const char *driver; /**< Driver name */ + pa_source_flags_t flags; /**< Flags */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ + pa_usec_t configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */ } pa_source_info; /** Callback prototype for pa_context_get_source_info_by_name() and friends */ @@ -261,16 +301,34 @@ pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t id, pa /** 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 */ +/** Set the volume of a source device specified by its index */ +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a source device specified by its name */ +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a source device specified by its index */ +pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a source device specified by its name */ +pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Server */ + +/** Server information. Please note that this structure can be + * extended as part of evolutionary API updates at any time in any new + * release. */ typedef struct pa_server_info { const char *user_name; /**< User name of the daemon process */ const char *host_name; /**< Host name the daemon is running on */ const char *server_version; /**< Version string of the daemon */ const char *server_name; /**< Server package name (usually "pulseaudio") */ pa_sample_spec sample_spec; /**< Default sample specification */ - const char *default_sink_name; /**< Name of default sink. \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 */ + const char *default_sink_name; /**< Name of default sink. */ + const char *default_source_name; /**< Name of default sink. */ + uint32_t cookie; /**< A random cookie for identifying this instance of PulseAudio. */ } pa_server_info; /** Callback prototype for pa_context_get_server_info() */ @@ -279,7 +337,13 @@ typedef void (*pa_server_info_cb_t) (pa_context *c, const pa_server_info*i, void /** 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 */ +/** @} */ + +/** @{ \name Modules */ + +/** Stores information about modules. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_module_info { uint32_t index; /**< Index of the module */ const char*name, /**< Name of the module */ @@ -297,12 +361,28 @@ pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_ /** 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 */ +/** Callback prototype for pa_context_load_module() */ +typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata); + +/** Load a module. */ +pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata); + +/** Unload a module. */ +pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Clients */ + +/** Stores information about clients. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_client_info { uint32_t index; /**< Index of this client */ const char *name; /**< Name of this client */ uint32_t owner_module; /**< Index of the owning module, or PA_INVALID_INDEX */ - const char *driver; /**< Driver name \since 0.8 */ + const char *driver; /**< Driver name */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ } pa_client_info; /** Callback prototype for pa_context_get_client_info() and firends*/ @@ -314,9 +394,18 @@ pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_ /** 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 */ +/** Kill a client. */ +pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Sink Inputs */ + +/** Stores information about sink inputs. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_sink_input_info { - uint32_t index; /**< Index of the sink input */ + 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 */ @@ -326,8 +415,10 @@ typedef struct pa_sink_input_info { 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 */ + const char *resample_method; /**< Thre resampling method used by this sink input. */ + const char *driver; /**< Driver name */ + int mute; /**< Stream muted \since 0.9.7 */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ } pa_sink_input_info; /** Callback prototype for pa_context_get_sink_input_info() and firends*/ @@ -339,19 +430,41 @@ pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sin /** 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 */ +/** Move the specified sink input to a different sink. \since 0.9.5 */ +pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, const char *sink_name, pa_context_success_cb_t cb, void* userdata); + +/** Move the specified sink input to a different sink. \since 0.9.5 */ +pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata); + +/** Set the volume of a sink input stream */ +pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink input stream \since 0.9.7 */ +pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Kill a sink input. */ +pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Source Outputs */ + +/** Stores information about source outputs. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_source_output_info { - uint32_t index; /**< Index of the sink input */ + 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 */ + 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_usec_t buffer_usec; /**< Latency due to buffering in the source output, see pa_latency_info for details. */ + pa_usec_t source_usec; /**< Latency of the source device, see pa_latency_info for details. */ + const char *resample_method; /**< Thre resampling method used by this source output. */ + const char *driver; /**< Driver name */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ } pa_source_output_info; /** Callback prototype for pa_context_get_source_output_info() and firends*/ @@ -363,40 +476,34 @@ pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_ /** 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); +/** Move the specified source output to a different source. \since 0.9.5 */ +pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, const char *source_name, pa_context_success_cb_t cb, void* userdata); -/** 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); +/** Move the specified source output to a different source. \since 0.9.5 */ +pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata); -/** Set the volume of a 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); +/** Suspend/Resume a source. \since 0.9.7 */ +pa_operation* pa_context_suspend_source_by_name(pa_context *c, const char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata); -/** 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); +/** 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); -/** 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); +/** Kill a source output. */ +pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); -/** 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); +/** @{ \name Statistics */ -/** Memory block statistics */ +/** Memory block statistics. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_stat_info { uint32_t memblock_total; /**< Currently allocated memory blocks */ uint32_t memblock_total_size; /**< 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 */ + uint32_t scache_size; /**< Total size of all sample cache entries. */ } pa_stat_info; /** Callback prototype for pa_context_stat() */ @@ -405,7 +512,13 @@ typedef void (*pa_stat_info_cb_t) (pa_context *c, const pa_stat_info *i, void *u /** 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 */ +/** @} */ + +/** @{ \name Cached Samples */ + +/** Stores information about sample cache entries. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_sample_info { uint32_t index; /**< Index of this entry */ const char *name; /**< Name of this entry */ @@ -413,9 +526,10 @@ typedef struct pa_sample_info { 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 */ + uint32_t bytes; /**< Length of this sample in bytes. */ + int lazy; /**< Non-zero when this is a lazy cache entry. */ + const char *filename; /**< In case this is a lazy cache entry, the filename for the sound file to be loaded on demand. */ + pa_proplist *proplist; /**< Property list for this sample. \since 0.9.11 */ } pa_sample_info; /** Callback prototype for pa_context_get_sample_info_by_name() and firends */ @@ -430,31 +544,21 @@ pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, p /** 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); +/** \cond fulldocs */ -/** 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); +/** @{ \name Autoload Entries */ -/** Type of an autoload entry. \since 0.5 */ +/** Type of an autoload entry. */ 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 */ +/** Stores information about autoload entries. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ typedef struct pa_autoload_info { uint32_t index; /**< Index of this autoload entry */ const char *name; /**< Name of the sink or source */ @@ -466,35 +570,27 @@ typedef struct 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); +/** Get info about a specific autoload entry. */ +pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED; -/** 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); +/** Get info about a specific autoload entry. */ +pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED; -/** 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); +/** Get the complete list of autoload entries. */ +pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED; -/** 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); +/** Add a new autoload entry. */ +pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t, void* userdata) PA_GCC_DEPRECATED; -/** 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); +/** Remove an autoload entry. */ +pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) PA_GCC_DEPRECATED; -/** 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); +/** Remove an autoload entry. */ +pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) PA_GCC_DEPRECATED; -/** 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); +/** \endcond */ PA_C_DECL_END diff --git a/src/pulse/mainloop-api.c b/src/pulse/mainloop-api.c index 2e20b446..90aff164 100644 --- a/src/pulse/mainloop-api.c +++ b/src/pulse/mainloop-api.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -23,12 +23,12 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <pulse/xmalloc.h> +#include <pulse/gccmacro.h> -#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> #include "mainloop-api.h" @@ -39,32 +39,37 @@ struct once_info { static void once_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { struct once_info *i = userdata; - assert(m && i && i->callback); + pa_assert(m); + pa_assert(i); + + pa_assert(i->callback); i->callback(m, i->userdata); - assert(m->defer_free); + 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; - assert(m && i); + + 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; - assert(m && callback); + + pa_assert(m); + pa_assert(callback); i = pa_xnew(struct once_info, 1); i->callback = callback; i->userdata = userdata; - assert(m->defer_new); - e = m->defer_new(m, once_callback, i); - assert(e); + 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 index 7b7075ae..53c7411e 100644 --- a/src/pulse/mainloop-api.h +++ b/src/pulse/mainloop-api.h @@ -1,21 +1,22 @@ #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 @@ -28,7 +29,7 @@ #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 diff --git a/src/pulse/mainloop-signal.c b/src/pulse/mainloop-signal.c index d651462b..9161dec4 100644 --- a/src/pulse/mainloop-signal.c +++ b/src/pulse/mainloop-signal.c @@ -1,18 +1,19 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +25,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <signal.h> #include <errno.h> #include <stdlib.h> @@ -36,12 +36,13 @@ #include <windows.h> #endif -#include <pulsecore/core-error.h> #include <pulse/xmalloc.h> +#include <pulse/gccmacro.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" @@ -52,9 +53,9 @@ struct pa_signal_event { #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_cb_t callback; + pa_signal_destroy_cb_t destroy_callback; pa_signal_event *previous, *next; }; @@ -64,18 +65,25 @@ 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; + pa_signal_event *s; - for (s = signals; s; s = s->next) + for (s = signals; s; s = s->next) if (s->sig == sig) { - assert(s->callback); + pa_assert(s->callback); s->callback(a, s, sig, s->userdata); break; } @@ -84,7 +92,12 @@ static void dispatch(pa_mainloop_api*a, int sig) { 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; - assert(a && e && f == PA_IO_EVENT_INPUT && e == io_event && fd == signal_pipe[0]); + + 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) @@ -93,7 +106,7 @@ static void callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags pa_log("read(): %s", pa_cstrerror(errno)); return; } - + if (r != sizeof(sig)) { pa_log("short read()"); return; @@ -104,56 +117,59 @@ static void callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags int pa_signal_init(pa_mainloop_api *a) { - assert(!api && a && signal_pipe[0] == -1 && signal_pipe[1] == -1 && !io_event); + 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_nonblock_fd(signal_pipe[0]); - pa_make_nonblock_fd(signal_pipe[1]); - pa_fd_set_cloexec(signal_pipe[0], 1); - pa_fd_set_cloexec(signal_pipe[1], 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; - io_event = api->io_new(api, signal_pipe[0], PA_IO_EVENT_INPUT, callback, NULL); - assert(io_event); + 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) { - assert(api && signal_pipe[0] >= 0 && signal_pipe[1] >= 0 && io_event); - while (signals) pa_signal_free(signals); - - api->io_free(io_event); - io_event = NULL; - close(signal_pipe[0]); - close(signal_pipe[1]); - signal_pipe[0] = signal_pipe[1] = -1; + if (io_event) { + pa_assert(api); + api->io_free(io_event); + io_event = NULL; + } + + pa_close_pipe(signal_pipe); api = NULL; } -pa_signal_event* pa_signal_new(int sig, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata) { +pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t _callback, void *userdata) { pa_signal_event *e = NULL; #ifdef HAVE_SIGACTION struct sigaction sa; #endif - assert(sig > 0 && _callback); - + pa_assert(sig > 0); + pa_assert(_callback); + for (e = signals; e; e = e->next) if (e->sig == sig) goto fail; - - e = pa_xmalloc(sizeof(pa_signal_event)); + + e = pa_xnew(pa_signal_event, 1); e->sig = sig; e->callback = _callback; e->userdata = userdata; @@ -164,7 +180,7 @@ pa_signal_event* pa_signal_new(int sig, void (*_callback) (pa_mainloop_api *api, 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) @@ -183,7 +199,7 @@ fail: } void pa_signal_free(pa_signal_event *e) { - assert(e); + pa_assert(e); if (e->next) e->next->previous = e->previous; @@ -193,18 +209,19 @@ void pa_signal_free(pa_signal_event *e) { signals = e->next; #ifdef HAVE_SIGACTION - sigaction(e->sig, &e->saved_sigaction, NULL); + pa_assert_se(sigaction(e->sig, &e->saved_sigaction, NULL) == 0); #else - signal(e->sig, e->saved_handler); + 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)) { - assert(e); +void pa_signal_set_destroy(pa_signal_event *e, pa_signal_destroy_cb_t _callback) { + pa_assert(e); + e->destroy_callback = _callback; } diff --git a/src/pulse/mainloop-signal.h b/src/pulse/mainloop-signal.h index 0721c1f5..a6c16f2f 100644 --- a/src/pulse/mainloop-signal.h +++ b/src/pulse/mainloop-signal.h @@ -1,21 +1,22 @@ #ifndef foomainloopsignalhfoo #define foomainloopsignalhfoo -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -36,23 +37,27 @@ PA_C_DECL_BEGIN * signals. However, you may hook signal support into an abstract main loop via the routines defined herein. */ +/** An opaque UNIX signal event source object */ +typedef struct pa_signal_event pa_signal_event; + +typedef void (*pa_signal_cb_t) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata); + +typedef void (*pa_signal_destroy_cb_t) (pa_mainloop_api *api, pa_signal_event*e, void *userdata); + /** Initialize the UNIX signal subsystem and bind it to the specified main loop */ int pa_signal_init(pa_mainloop_api *api); /** Cleanup the signal subsystem */ void pa_signal_done(void); -/** 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); +pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t callback, void *userdata); /** Free a UNIX signal event source object */ void pa_signal_free(pa_signal_event *e); /** Set a function that is called when the signal event source is destroyed. Use this to free the userdata argument if required */ -void pa_signal_set_destroy(pa_signal_event *e, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata)); +void pa_signal_set_destroy(pa_signal_event *e, pa_signal_destroy_cb_t callback); PA_C_DECL_END diff --git a/src/pulse/mainloop.c b/src/pulse/mainloop.c index f7b15537..aaed3caf 100644 --- a/src/pulse/mainloop.c +++ b/src/pulse/mainloop.c @@ -1,18 +1,19 @@ -/* $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 @@ -28,40 +29,39 @@ #include <unistd.h> #include <stdlib.h> #include <string.h> -#include <assert.h> #include <fcntl.h> #include <errno.h> -#ifdef HAVE_SYS_POLL_H -#include <sys/poll.h> +#ifdef HAVE_POLL_H +#include <poll.h> #else -#include "../pulsecore/poll.h" +#include <pulsecore/poll.h> #endif -#include "../pulsecore/winsock.h" - #ifndef HAVE_PIPE -#include "../pulsecore/pipe.h" +#include <pulsecore/pipe.h> #endif -#include <pulsecore/core-error.h> #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; @@ -154,17 +154,17 @@ static pa_io_event* mainloop_io_new( pa_io_event_flags_t events, pa_io_event_cb_t callback, void *userdata) { - + pa_mainloop *m; pa_io_event *e; - assert(a); - assert(a->userdata); - assert(fd >= 0); - assert(callback); - + pa_assert(a); + pa_assert(a->userdata); + pa_assert(fd >= 0); + pa_assert(callback); + m = a->userdata; - assert(a == &m->api); + pa_assert(a == &m->api); e = pa_xnew(pa_io_event, 1); e->mainloop = m; @@ -173,7 +173,7 @@ static pa_io_event* mainloop_io_new( e->fd = fd; e->events = events; e->pollfd = NULL; - + e->callback = callback; e->userdata = userdata; e->destroy_callback = NULL; @@ -192,7 +192,7 @@ static pa_io_event* mainloop_io_new( if ((select((SELECT_TYPE_ARG1) fd, NULL, NULL, SELECT_TYPE_ARG234 &xset, SELECT_TYPE_ARG5 &tv) == -1) && (WSAGetLastError() == WSAENOTSOCK)) { - pa_log_warn("WARNING: cannot monitor non-socket file descriptors."); + pa_log_warn("Cannot monitor non-socket file descriptors."); e->dead = 1; } } @@ -208,12 +208,12 @@ static pa_io_event* mainloop_io_new( } static void mainloop_io_enable(pa_io_event *e, pa_io_event_flags_t events) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); if (e->events == events) return; - + e->events = events; if (e->pollfd) @@ -225,8 +225,8 @@ static void mainloop_io_enable(pa_io_event *e, pa_io_event_flags_t events) { } static void mainloop_io_free(pa_io_event *e) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); e->dead = 1; e->mainloop->io_events_please_scan ++; @@ -238,8 +238,8 @@ static void mainloop_io_free(pa_io_event *e) { } static void mainloop_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t callback) { - assert(e); - + pa_assert(e); + e->destroy_callback = callback; } @@ -252,12 +252,12 @@ static pa_defer_event* mainloop_defer_new( pa_mainloop *m; pa_defer_event *e; - assert(a); - assert(a->userdata); - assert(callback); - + pa_assert(a); + pa_assert(a->userdata); + pa_assert(callback); + m = a->userdata; - assert(a == &m->api); + pa_assert(a == &m->api); e = pa_xnew(pa_defer_event, 1); e->mainloop = m; @@ -265,7 +265,7 @@ static pa_defer_event* mainloop_defer_new( e->enabled = 1; m->n_enabled_defer_events++; - + e->callback = callback; e->userdata = userdata; e->destroy_callback = NULL; @@ -278,36 +278,37 @@ static pa_defer_event* mainloop_defer_new( } static void mainloop_defer_enable(pa_defer_event *e, int b) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); if (e->enabled && !b) { - assert(e->mainloop->n_enabled_defer_events > 0); + 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) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); e->dead = 1; e->mainloop->defer_events_please_scan ++; if (e->enabled) { - assert(e->mainloop->n_enabled_defer_events > 0); + 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) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); e->destroy_callback = callback; } @@ -318,16 +319,16 @@ static pa_time_event* mainloop_time_new( const struct timeval *tv, pa_time_event_cb_t callback, void *userdata) { - + pa_mainloop *m; pa_time_event *e; - assert(a); - assert(a->userdata); - assert(callback); - + pa_assert(a); + pa_assert(a->userdata); + pa_assert(callback); + m = a->userdata; - assert(a == &m->api); + pa_assert(a == &m->api); e = pa_xnew(pa_time_event, 1); e->mainloop = m; @@ -339,7 +340,7 @@ static pa_time_event* mainloop_time_new( m->n_enabled_time_events++; if (m->cached_next_time_event) { - assert(m->cached_next_time_event->enabled); + 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; @@ -354,16 +355,16 @@ static pa_time_event* mainloop_time_new( if (e->enabled) pa_mainloop_wakeup(m); - + return e; } static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); if (e->enabled && !tv) { - assert(e->mainloop->n_enabled_time_events > 0); + 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++; @@ -374,8 +375,8 @@ static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) { } if (e->mainloop->cached_next_time_event && e->enabled) { - assert(e->mainloop->cached_next_time_event->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) @@ -383,26 +384,27 @@ static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) { } static void mainloop_time_free(pa_time_event *e) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); e->dead = 1; e->mainloop->time_events_please_scan ++; if (e->enabled) { - assert(e->mainloop->n_enabled_time_events > 0); + 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) { - assert(e); - assert(!e->dead); + pa_assert(e); + pa_assert(!e->dead); e->destroy_callback = callback; } @@ -411,15 +413,15 @@ static void mainloop_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb static void mainloop_quit(pa_mainloop_api*a, int retval) { pa_mainloop *m; - - assert(a); - assert(a->userdata); + + pa_assert(a); + pa_assert(a->userdata); m = a->userdata; - assert(a == &m->api); + pa_assert(a == &m->api); pa_mainloop_quit(m, retval); } - + static const pa_mainloop_api vtable = { .userdata = NULL, @@ -432,12 +434,12 @@ static const pa_mainloop_api vtable = { .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, }; @@ -453,8 +455,10 @@ pa_mainloop *pa_mainloop_new(void) { return NULL; } - pa_make_nonblock_fd(m->wakeup_pipe[0]); - pa_make_nonblock_fd(m->wakeup_pipe[1]); + 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); @@ -466,7 +470,7 @@ pa_mainloop *pa_mainloop_new(void) { m->cached_next_time_event = NULL; m->prepared_timeout = 0; - + m->pollfds = NULL; m->max_pollfds = m->n_pollfds = 0; m->rebuild_pollfds = 1; @@ -481,7 +485,7 @@ pa_mainloop *pa_mainloop_new(void) { m->poll_func = NULL; m->poll_func_userdata = NULL; m->poll_func_ret = -1; - + return m; } @@ -494,18 +498,18 @@ static void cleanup_io_events(pa_mainloop *m, int force) { 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) { - assert(m->io_events_please_scan > 0); + 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; @@ -514,7 +518,7 @@ static void cleanup_io_events(pa_mainloop *m, int force) { e = n; } - assert(m->io_events_please_scan == 0); + pa_assert(m->io_events_please_scan == 0); } static void cleanup_time_events(pa_mainloop *m, int force) { @@ -526,30 +530,31 @@ static void cleanup_time_events(pa_mainloop *m, int force) { 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) { - assert(m->time_events_please_scan > 0); + pa_assert(m->time_events_please_scan > 0); m->time_events_please_scan--; } if (!e->dead && e->enabled) { - assert(m->n_enabled_time_events > 0); + 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; } - assert(m->time_events_please_scan == 0); + pa_assert(m->time_events_please_scan == 0); } static void cleanup_defer_events(pa_mainloop *m, int force) { @@ -561,35 +566,36 @@ static void cleanup_defer_events(pa_mainloop *m, int force) { 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) { - assert(m->defer_events_please_scan > 0); + pa_assert(m->defer_events_please_scan > 0); m->defer_events_please_scan--; } if (!e->dead && e->enabled) { - assert(m->n_enabled_defer_events > 0); + 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; } - assert(m->defer_events_please_scan == 0); + pa_assert(m->defer_events_please_scan == 0); } void pa_mainloop_free(pa_mainloop* m) { - assert(m); + pa_assert(m); cleanup_io_events(m, 1); cleanup_defer_events(m, 1); @@ -597,16 +603,13 @@ void pa_mainloop_free(pa_mainloop* m) { pa_xfree(m->pollfds); - if (m->wakeup_pipe[0] >= 0) - close(m->wakeup_pipe[0]); - if (m->wakeup_pipe[1] >= 0) - close(m->wakeup_pipe[1]); + pa_close_pipe(m->wakeup_pipe); pa_xfree(m); } static void scan_dead(pa_mainloop *m) { - assert(m); + pa_assert(m); if (m->io_events_please_scan) cleanup_io_events(m, 0); @@ -663,13 +666,14 @@ static int dispatch_pollfds(pa_mainloop *m) { pa_io_event *e; int r = 0, k; - assert(m->poll_func_ret > 0); - + 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; - - assert(e->pollfd->fd == e->fd && e->callback); + + 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++; @@ -690,8 +694,8 @@ static int dispatch_defer(pa_mainloop *m) { for (e = m->defer_events; e && !m->quit; e = e->next) { if (e->dead || !e->enabled) continue; - - assert(e->callback); + + pa_assert(e->callback); e->callback(&m->api, e, e->userdata); r++; } @@ -701,11 +705,11 @@ static int dispatch_defer(pa_mainloop *m) { static pa_time_event* find_next_time_event(pa_mainloop *m) { pa_time_event *t, *n = NULL; - assert(m); + 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) @@ -733,11 +737,11 @@ static int calc_next_timeout(pa_mainloop *m) { return -1; t = find_next_time_event(m); - assert(t); + pa_assert(t); if (t->timeval.tv_sec <= 0) return 0; - + pa_gettimeofday(&now); if (pa_timeval_cmp(&t->timeval, &now) <= 0) @@ -751,7 +755,7 @@ static int dispatch_timeout(pa_mainloop *m) { pa_time_event *e; struct timeval now; int r = 0; - assert(m); + pa_assert(m); if (m->n_enabled_time_events <= 0) return 0; @@ -759,12 +763,12 @@ static int dispatch_timeout(pa_mainloop *m) { 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) { - assert(e->callback); + pa_assert(e->callback); /* Disable time event */ mainloop_time_restart(e, NULL); @@ -780,7 +784,7 @@ static int dispatch_timeout(pa_mainloop *m) { void pa_mainloop_wakeup(pa_mainloop *m) { char c = 'W'; - assert(m); + 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); @@ -791,7 +795,7 @@ void pa_mainloop_wakeup(pa_mainloop *m) { static void clear_wakeup(pa_mainloop *m) { char c[10]; - assert(m); + pa_assert(m); if (m->wakeup_pipe[0] < 0) return; @@ -803,8 +807,8 @@ static void clear_wakeup(pa_mainloop *m) { } int pa_mainloop_prepare(pa_mainloop *m, int timeout) { - assert(m); - assert(m->state == STATE_PASSIVE); + pa_assert(m); + pa_assert(m->state == STATE_PASSIVE); clear_wakeup(m); scan_dead(m); @@ -815,7 +819,7 @@ int pa_mainloop_prepare(pa_mainloop *m, int timeout) { 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; @@ -830,8 +834,8 @@ quit: } int pa_mainloop_poll(pa_mainloop *m) { - assert(m); - assert(m->state == STATE_PREPARED); + pa_assert(m); + pa_assert(m->state == STATE_PREPARED); if (m->quit) goto quit; @@ -841,8 +845,8 @@ int pa_mainloop_poll(pa_mainloop *m) { if (m->n_enabled_defer_events ) m->poll_func_ret = 0; else { - assert(!m->rebuild_pollfds); - + 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 @@ -867,28 +871,28 @@ quit: int pa_mainloop_dispatch(pa_mainloop *m) { int dispatched = 0; - assert(m); - assert(m->state == STATE_POLLED); + 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) + 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; @@ -899,13 +903,13 @@ quit: } int pa_mainloop_get_retval(pa_mainloop *m) { - assert(m); + pa_assert(m); return m->retval; } int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval) { int r; - assert(m); + pa_assert(m); if ((r = pa_mainloop_prepare(m, block ? -1 : 0)) < 0) goto quit; @@ -919,7 +923,7 @@ int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval) { return r; quit: - + if ((r == -2) && retval) *retval = pa_mainloop_get_retval(m); return r; @@ -927,7 +931,7 @@ quit: int pa_mainloop_run(pa_mainloop *m, int *retval) { int r; - + while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0); if (r == -2) @@ -939,7 +943,7 @@ int pa_mainloop_run(pa_mainloop *m, int *retval) { } void pa_mainloop_quit(pa_mainloop *m, int retval) { - assert(m); + pa_assert(m); m->quit = 1; m->retval = retval; @@ -947,12 +951,12 @@ void pa_mainloop_quit(pa_mainloop *m, int retval) { } pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m) { - assert(m); + pa_assert(m); return &m->api; } void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata) { - assert(m); + 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 index 8abd8fe4..907e94a7 100644 --- a/src/pulse/mainloop.h +++ b/src/pulse/mainloop.h @@ -1,21 +1,22 @@ #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 @@ -65,7 +66,7 @@ struct pollfd; */ /** \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 diff --git a/src/pulse/operation.c b/src/pulse/operation.c index 8d896d7d..13b470a8 100644 --- a/src/pulse/operation.c +++ b/src/pulse/operation.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,21 +23,26 @@ #include <config.h> #endif -#include <assert.h> - #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; - assert(c); + pa_assert(c); + + if (!(o = pa_flist_pop(PA_STATIC_FLIST_GET(operations)))) + o = pa_xnew(pa_operation, 1); - o = pa_xnew(pa_operation, 1); - o->ref = 1; + PA_REFCNT_INIT(o); o->context = c; o->stream = s; + o->private = NULL; o->state = PA_OPERATION_RUNNING; o->callback = cb; @@ -46,32 +51,50 @@ pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb /* 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) { - assert(o); - assert(o->ref >= 1); - - o->ref++; + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + PA_REFCNT_INC(o); return o; } - void pa_operation_unref(pa_operation *o) { - assert(o); - assert(o->ref >= 1); + 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 ((--(o->ref)) == 0) { - assert(!o->context); - assert(!o->stream); - pa_xfree(o); + if (pa_flist_push(PA_STATIC_FLIST_GET(operations), o) < 0) + pa_xfree(o); } } +static void operation_unlink(pa_operation *o) { + pa_assert(o); + + if (o->context) { + pa_assert(PA_REFCNT_VALUE(o) >= 2); + + PA_LLIST_REMOVE(pa_operation, o->context->operations, o); + pa_operation_unref(o); + + o->context = NULL; + } + + o->stream = NULL; + o->callback = NULL; + o->userdata = NULL; +} + static void operation_set_state(pa_operation *o, pa_operation_state_t st) { - assert(o); - assert(o->ref >= 1); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); if (st == o->state) return; @@ -80,41 +103,29 @@ static void operation_set_state(pa_operation *o, pa_operation_state_t st) { o->state = st; - if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) { - - if (o->context) { - assert(o->ref >= 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; - } + if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) + operation_unlink(o); pa_operation_unref(o); } void pa_operation_cancel(pa_operation *o) { - assert(o); - assert(o->ref >= 1); - + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + operation_set_state(o, PA_OPERATION_CANCELED); } void pa_operation_done(pa_operation *o) { - assert(o); - assert(o->ref >= 1); - + 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) { - assert(o); - assert(o->ref >= 1); + 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 index b544e08e..188e2cb9 100644 --- a/src/pulse/operation.h +++ b/src/pulse/operation.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c new file mode 100644 index 00000000..74aea20a --- /dev/null +++ b/src/pulse/proplist.c @@ -0,0 +1,321 @@ +/*** + 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) { + pa_assert(p); + + pa_proplist_clear(p); + pa_hashmap_free(MAKE_HASHMAP(p), NULL, NULL); +} + +/** Will accept only valid UTF-8 */ +int pa_proplist_sets(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; +} + +/** Will accept only valid UTF-8 */ +int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) { + va_list ap; + int r; + char *t; + + pa_assert(p); + pa_assert(key); + + if (!property_name_valid(key) || !pa_utf8_valid(format)) + return -1; + + va_start(ap, format); + t = pa_vsprintf_malloc(format, ap); + va_end(ap); + + r = pa_proplist_sets(p, key, t); + + pa_xfree(t); + return r; +} + +int pa_proplist_set(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_update(pa_proplist *p, pa_update_mode_t mode, pa_proplist *other) { + struct property *prop; + void *state = NULL; + + pa_assert(p); + pa_assert(mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE); + pa_assert(other); + + if (mode == PA_UPDATE_SET) + pa_proplist_clear(p); + + while ((prop = pa_hashmap_iterate(MAKE_HASHMAP(other), &state, NULL))) { + + if (mode == PA_UPDATE_MERGE && pa_proplist_contains(p, prop->key)) + continue; + + pa_assert_se(pa_proplist_set(p, prop->key, prop->value, prop->nbytes) == 0); + } +} + +int pa_proplist_unset(pa_proplist *p, const char *key) { + 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 -2; + + property_free(prop); + return 0; +} + +int pa_proplist_unset_many(pa_proplist *p, const char * const keys[]) { + const char * const * k; + int n = 0; + + pa_assert(p); + pa_assert(keys); + + for (k = keys; *k; k++) + if (!property_name_valid(*k)) + return -1; + + for (k = keys; *k; k++) + if (pa_proplist_unset(p, *k) >= 0) + n++; + + return n; +} + +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; +} + +void pa_proplist_clear(pa_proplist *p) { + struct property *prop; + pa_assert(p); + + while ((prop = pa_hashmap_steal_first(MAKE_HASHMAP(p)))) + property_free(prop); +} + +pa_proplist* pa_proplist_copy(pa_proplist *template) { + pa_proplist *p; + + pa_assert_se(p = pa_proplist_new()); + + if (template) + pa_proplist_update(p, PA_UPDATE_REPLACE, template); + + return p; +} diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h new file mode 100644 index 00000000..f75cca54 --- /dev/null +++ b/src/pulse/proplist.h @@ -0,0 +1,217 @@ +#ifndef foopulseproplisthfoo +#define foopulseproplisthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2007 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <sys/types.h> + +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> + +PA_C_DECL_BEGIN + +/* Defined properties: + * + * media.name "Guns'N'Roses: Civil War" + * media.title "Civil War" + * media.artist "Guns'N'Roses" + * media.language "de_DE" + * media.filename + * media.icon + * media.icon_name + * media.role video, music, game, event, phone, production, filter, abstract, stream + * event.id button-click, session-login + * event.mouse.x + * event.mouse.y + * event.mouse.hpos + * event.mouse.vpos + * event.mouse.button + * window.name + * window.id + * window.icon + * window.icon_name + * window.x11.display + * window.x11.screen + * window.x11.monitor + * window.x11.xid + * application.name "Rhythmbox Media Player" + * application.id "org.gnome.rhythmbox" + * application.version + * application.icon + * application.icon_name + * application.language + * application.process.id + * application.process.binary + * application.process.user + * application.process.host + * device.string + * device.api oss, alsa, sunaudio + * device.description + * device.bus_path + * device.serial + * device.vendor_product_id + * device.class sound, modem, monitor, filter, abstract + * device.form_factor laptop-speakers, external-speakers, telephone, tv-capture, webcam-capture, microphone-capture, headset + * device.connector isa, pci, usb, firewire, bluetooth + * device.access_mode mmap, mmap_rewrite, serial + * device.master_device + * device.bufferin.buffer_size + * device.bufferin.fragment_size + */ +#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_EVENT_ID "event.id" +#define PA_PROP_EVENT_MOUSE_X "event.mouse.x" +#define PA_PROP_EVENT_MOUSE_Y "event.mouse.y" +#define PA_PROP_EVENT_MOUSE_HPOS "event.mouse.hpos" +#define PA_PROP_EVENT_MOUSE_VPOS "event.mouse.vpos" +#define PA_PROP_EVENT_MOUSE_BUTTON "event.mouse.button" +#define PA_PROP_WINDOW_NAME "window.name" +#define PA_PROP_WINDOW_ID "window.id" +#define PA_PROP_WINDOW_ICON "window.icon" +#define PA_PROP_WINDOW_ICON_NAME "window.icon_name" +#define PA_PROP_WINDOW_X11_DISPLAY "window.x11.display" +#define PA_PROP_WINDOW_X11_SCREEN "window.x11.screen" +#define PA_PROP_WINDOW_X11_MONITOR "window.x11.monitor" +#define PA_PROP_WINDOW_X11_XID "window.x11.xid" +#define PA_PROP_APPLICATION_NAME "application.name" +#define PA_PROP_APPLICATION_ID "application.id" +#define PA_PROP_APPLICATION_VERSION "application.version" +#define PA_PROP_APPLICATION_ICON "application.icon" +#define PA_PROP_APPLICATION_ICON_NAME "application.icon_name" +#define PA_PROP_APPLICATION_LANGUAGE "application.language" +#define PA_PROP_APPLICATION_PROCESS_ID "application.process.id" +#define PA_PROP_APPLICATION_PROCESS_BINARY "application.process.binary" +#define PA_PROP_APPLICATION_PROCESS_USER "application.process.user" +#define PA_PROP_APPLICATION_PROCESS_HOST "application.process.host" +#define PA_PROP_DEVICE_STRING "device.string" +#define PA_PROP_DEVICE_API "device.api" +#define PA_PROP_DEVICE_DESCRIPTION "device.description" +#define PA_PROP_DEVICE_BUS_PATH "device.bus_path" +#define PA_PROP_DEVICE_SERIAL "device.serial" +#define PA_PROP_DEVICE_VENDOR_PRODUCT_ID "device.vendor_product_id" +#define PA_PROP_DEVICE_CLASS "device.class" +#define PA_PROP_DEVICE_FORM_FACTOR "device.form_factor" +#define PA_PROP_DEVICE_CONNECTOR "device.connector" +#define PA_PROP_DEVICE_ACCESS_MODE "device.access_mode" +#define PA_PROP_DEVICE_MASTER_DEVICE "device.master_device" +#define PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE "device.buffering.buffer_size" +#define PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE "device.buffering.fragment_size" + +/** A property list object. Basically a dictionary with UTF-8 strings + * as keys and arbitrary data as values. \since 0.9.11 */ +typedef struct pa_proplist pa_proplist; + +/** Allocate a property list. \since 0.9.11 */ +pa_proplist* pa_proplist_new(void); + +/** Free the property list. \since 0.9.11 */ +void pa_proplist_free(pa_proplist* p); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid + * UTF-8. \since 0.9.11 */ +int pa_proplist_sets(pa_proplist *p, const char *key, const char *value); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid + * UTF-8. The data can be passed as printf()-style format string with + * arguments. \since 0.9.11 */ +int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) PA_GCC_PRINTF_ATTR(3,4); + +/** Append a new arbitrary data entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. \since 0.9.11 */ +int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nbytes); + +/* Return a string entry for the specified key. Will return NULL if + * the data is not valid UTF-8. Will return a NUL-terminated string in + * an internally allocated buffer. The caller should make a copy of + * the data before accessing the property list again. \since 0.9.11 */ +const char *pa_proplist_gets(pa_proplist *p, const char *key); + +/** Return the the value for the specified key. Will return a + * NUL-terminated string for string entries. The pointer returned will + * point to an internally allocated buffer. The caller should make a + * copy of the data before the property list is accessed again. \since + * 0.9.11 */ +int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *nbytes); + +/** Update mode enum for pa_proplist_update(). \since 0.9.11 */ +typedef enum pa_update_mode { + PA_UPDATE_SET, /*< Replace the entirey property list with the new one. Don't keep any of the old data around */ + PA_UPDATE_MERGE, /*< Merge new property list into the existing one, not replacing any old entries if they share a common key with the new property list. */ + PA_UPDATE_REPLACE /*< Merge new property list into the existing one, replacing all old entries that share a common key with the new property list. */ +} pa_update_mode_t; + +/** Merge property list "other" into "p", adhering the merge mode as + * specified in "mode". \since 0.9.11 */ +void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, pa_proplist *other); + +/** Removes a single entry from the property list, identified be the + * specified key name. \since 0.9.11 */ +int pa_proplist_unset(pa_proplist *p, const char *key); + +/** Similar to pa_proplist_remove() but takes an array of keys to + * remove. The array should be terminated by a NULL pointer. Return -1 + * on failure, otherwise the number of entries actually removed (which + * might even be 0, if there where no matching entries to + * remove). \since 0.9.11 */ +int pa_proplist_unset_many(pa_proplist *p, const char * const keys[]); + +/** Iterate through the property list. The user should allocate a + * state variable of type void* and initialize it with NULL. A pointer + * to this variable should then be passed to pa_proplist_iterate() + * which should be called in a loop until it returns NULL which + * signifies EOL. The property list should not be modified during + * iteration through the list -- except for deleting the current + * looked at entry. On each invication this function will return the + * key string for the next entry. The keys in the property list do not + * have any particular order. \since 0.9.11 */ +const char *pa_proplist_iterate(pa_proplist *p, void **state); + +/** Format the property list nicely as a human readable string. Call pa_xfree() on the result. \since + * 0.9.11 */ +char *pa_proplist_to_string(pa_proplist *p); + +/** Returns 1 if an entry for the specified key is existant in the + * property list. \since 0.9.11 */ +int pa_proplist_contains(pa_proplist *p, const char *key); + +/** Remove all entries from the property list object. \since 0.9.11 */ +void pa_proplist_clear(pa_proplist *p); + +/** Allocate a new property list and copy over every single entry from + * the specific list. \since 0.9.11 */ +pa_proplist* pa_proplist_copy(pa_proplist *t); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h index 88cc326b..ee8a4bb8 100644 --- a/src/pulse/pulseaudio.h +++ b/src/pulse/pulseaudio.h @@ -1,21 +1,22 @@ #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 @@ -55,11 +56,11 @@ /** \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 * @@ -67,7 +68,7 @@ * 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 diff --git a/src/pulse/sample.c b/src/pulse/sample.c index 7ca418e1..4aef5bb0 100644 --- a/src/pulse/sample.c +++ b/src/pulse/sample.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,59 +25,64 @@ #endif #include <stdio.h> -#include <assert.h> #include <math.h> #include <string.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulse/timeval.h> + #include "sample.h" size_t pa_sample_size(const pa_sample_spec *spec) { - assert(spec); - - switch (spec->format) { - case PA_SAMPLE_U8: - case PA_SAMPLE_ULAW: - case PA_SAMPLE_ALAW: - return 1; - case PA_SAMPLE_S16LE: - case PA_SAMPLE_S16BE: - return 2; - case PA_SAMPLE_FLOAT32LE: - case PA_SAMPLE_FLOAT32BE: - return 4; - default: - assert(0); - return 0; - } + + 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) { - assert(spec); + pa_assert(spec); return pa_sample_size(spec) * spec->channels; } size_t pa_bytes_per_second(const pa_sample_spec *spec) { - assert(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) { - assert(spec); + pa_assert(spec); - return (pa_usec_t) (((double) length/pa_frame_size(spec)*1000000)/spec->rate); + return (((pa_usec_t) (length / pa_frame_size(spec)) * PA_USEC_PER_SEC) / spec->rate); } size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) { - assert(spec); + pa_assert(spec); - return (size_t) (((double) t * spec->rate / 1000000))*pa_frame_size(spec); + return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) * pa_frame_size(spec); } int pa_sample_spec_valid(const pa_sample_spec *spec) { - assert(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 || @@ -87,9 +93,13 @@ int pa_sample_spec_valid(const pa_sample_spec *spec) { } int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) { - assert(a && b); + pa_assert(a); + pa_assert(b); - return (a->format == b->format) && (a->rate == b->rate) && (a->channels == b->channels); + 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) { @@ -101,58 +111,77 @@ const char *pa_sample_format_to_string(pa_sample_format_t f) { [PA_SAMPLE_S16BE] = "s16be", [PA_SAMPLE_FLOAT32LE] = "float32le", [PA_SAMPLE_FLOAT32BE] = "float32be", + [PA_SAMPLE_S32LE] = "s32le", + [PA_SAMPLE_S32BE] = "s32be", }; - if (f >= PA_SAMPLE_MAX) + 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) { - assert(s && l && spec); - + pa_assert(s); + pa_assert(l); + pa_assert(spec); + if (!pa_sample_spec_valid(spec)) - snprintf(s, l, "Invalid"); + pa_snprintf(s, l, "Invalid"); else - snprintf(s, l, "%s %uch %uHz", pa_sample_format_to_string(spec->format), spec->channels, spec->rate); + 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) - snprintf(s, l, "%0.1f GiB", ((double) v)/1024/1024/1024); + pa_snprintf(s, l, "%0.1f GiB", ((double) v)/1024/1024/1024); else if (v >= ((unsigned) 1024)*1024) - snprintf(s, l, "%0.1f MiB", ((double) v)/1024/1024); + pa_snprintf(s, l, "%0.1f MiB", ((double) v)/1024/1024); else if (v >= (unsigned) 1024) - snprintf(s, l, "%0.1f KiB", ((double) v)/1024); + pa_snprintf(s, l, "%0.1f KiB", ((double) v)/1024); else - snprintf(s, l, "%u B", (unsigned) v); + 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) - return PA_SAMPLE_FLOAT32; + 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) + 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 index da32fdf0..1ba3f871 100644 --- a/src/pulse/sample.h +++ b/src/pulse/sample.h @@ -1,21 +1,22 @@ #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 @@ -24,8 +25,10 @@ #include <inttypes.h> #include <sys/types.h> +#include <sys/param.h> #include <math.h> +#include <pulse/gccmacro.h> #include <pulse/cdecl.h> /** \page sample Sample Format Specifications @@ -39,13 +42,15 @@ * * PulseAudio supports the following sample formats: * - * \li PA_SAMPLE_U8 - Unsigned 8 bit PCM. - * \li PA_SAMPLE_S16LE - Signed 16 bit PCM, little endian. - * \li PA_SAMPLE_S16BE - Signed 16 bit PCM, big endian. + * \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. * @@ -99,8 +104,19 @@ 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 +#define PA_CHANNELS_MAX 32U + +/** Maximum allowed sample rate */ +#define PA_RATE_MAX (48000U*4U) /** Sample format */ typedef enum pa_sample_format { @@ -111,6 +127,8 @@ typedef enum pa_sample_format { 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; @@ -120,19 +138,27 @@ typedef enum pa_sample_format { #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 */ @@ -145,35 +171,35 @@ typedef struct pa_sample_spec { 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 */ +/** Type for usec specifications (unsigned). Always 64 bit. */ 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); +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); +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); +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_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); +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); +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); +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); +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_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 diff --git a/src/pulse/scache.c b/src/pulse/scache.c index 5d29c5b3..5e31e7af 100644 --- a/src/pulse/scache.c +++ b/src/pulse/scache.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,12 +23,15 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <pulse/utf8.h> + #include <pulsecore/pstream-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/proplist-util.h> #include "internal.h" @@ -37,26 +40,41 @@ int pa_stream_connect_upload(pa_stream *s, size_t length) { pa_tagstruct *t; uint32_t tag; - - assert(s); + const char *name; + + 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); - + + if (!(name = pa_proplist_gets(s->proplist, PA_PROP_EVENT_ID))) + name = pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME); + + PA_CHECK_VALIDITY(s->context, name && *name && pa_utf8_valid(name), PA_ERR_INVALID); + pa_stream_ref(s); - + s->direction = PA_STREAM_UPLOAD; + s->flags = 0; t = pa_tagstruct_command(s->context, PA_COMMAND_CREATE_UPLOAD_STREAM, &tag); - pa_tagstruct_puts(t, s->name); + + pa_tagstruct_puts(t, name); pa_tagstruct_put_sample_spec(t, &s->sample_spec); pa_tagstruct_put_channel_map(t, &s->channel_map); pa_tagstruct_putu32(t, length); + + if (s->context->version >= 13) { + pa_init_proplist(s->proplist); + pa_tagstruct_put_proplist(t, s->proplist); + } + pa_pstream_send_tagstruct(s->context->pstream, t); pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); pa_stream_set_state(s, PA_STREAM_CREATING); - + pa_stream_unref(s); return 0; } @@ -64,7 +82,9 @@ int pa_stream_connect_upload(pa_stream *s, size_t length) { int pa_stream_finish_upload(pa_stream *s) { pa_tagstruct *t; uint32_t tag; - assert(s); + + 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); @@ -80,30 +100,136 @@ int pa_stream_finish_upload(pa_stream *s) { return 0; } +static void play_sample_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + uint32_t idx = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) + goto finish; + + success = 0; + } else if ((o->context->version >= 13 && pa_tagstruct_getu32(t, &idx) < 0) || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } else if (o->context->version >= 13 && idx == PA_INVALID_INDEX) + success = 0; + + if (o->callback) { + pa_context_success_cb_t cb = (pa_context_success_cb_t) o->callback; + cb(o->context, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +static void play_sample_with_proplist_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t idx; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) + goto finish; + + idx = PA_INVALID_INDEX; + } else if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_context_play_sample_cb_t cb = (pa_context_play_sample_cb_t) o->callback; + cb(o->context, idx, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + + pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char *dev, pa_volume_t volume, pa_context_success_cb_t cb, void *userdata) { pa_operation *o; pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); - + 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); + + if (c->version >= 13) { + pa_proplist *p = pa_proplist_new(); + pa_tagstruct_put_proplist(t, p); + pa_proplist_free(p); + } + pa_pstream_send_tagstruct(c->pstream, t); - pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, play_sample_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_context_play_sample_with_proplist(pa_context *c, const char *name, const char *dev, pa_volume_t volume, pa_proplist *p, pa_context_play_sample_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !dev || *dev, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, p, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + if (!dev) + dev = c->conf->default_sink; + + t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, dev); + pa_tagstruct_putu32(t, volume); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_proplist(t, p); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, play_sample_with_proplist_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); return o; } @@ -113,19 +239,19 @@ pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_conte pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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 index e32703d4..f380b4e8 100644 --- a/src/pulse/scache.h +++ b/src/pulse/scache.h @@ -1,21 +1,22 @@ #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 @@ -76,14 +77,25 @@ PA_C_DECL_BEGIN +/** Callback prototype for pa_context_play_sample_with_proplist(). The + * idx value is the index of the sink input object, or + * PA_INVALID_INDEX on failure. \since 0.9.11 */ +typedef void (*pa_context_play_sample_cb_t)(pa_context *c, uint32_t idx, void *userdata); + /** Make this stream a sample upload stream */ 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() */ +/** Finish the sample upload, the stream name will become the sample + * name. You cancel a sample 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 */ +/** Remove a sample from the sample cache. Returns an operation object which may be used to cancel the operation while it is running */ +pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Play a sample from the sample cache to the specified device. If + * the latter is NULL use the default sink. Returns an operation + * object */ pa_operation* pa_context_play_sample( pa_context *c /**< Context */, const char *name /**< Name of the sample to play */, @@ -92,8 +104,18 @@ pa_operation* pa_context_play_sample( 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); +/** Play a sample from the sample cache to the specified device, + * allowing specification of a property list for the playback + * stream. If the latter is NULL use the default sink. Returns an + * operation object. \since 0.9.11 */ +pa_operation* pa_context_play_sample_with_proplist( + pa_context *c /**< Context */, + const char *name /**< Name of the sample to play */, + const char *dev /**< Sink to play this sample on */, + pa_volume_t volume /**< Volume to play this sample with */ , + pa_proplist *proplist /**< Property list for this sound. The property list of the cached entry will be merged into this property list */, + pa_context_play_sample_cb_t cb /**< Call this function after successfully starting playback, or NULL */, + void *userdata /**< Userdata to pass to the callback */); PA_C_DECL_END diff --git a/src/pulse/simple.c b/src/pulse/simple.c index a41881bb..70396835 100644 --- a/src/pulse/simple.c +++ b/src/pulse/simple.c @@ -1,18 +1,19 @@ -/* $Id$ */ /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -25,7 +26,6 @@ #include <stdio.h> #include <string.h> -#include <assert.h> #include <stdlib.h> #include <pulse/pulseaudio.h> @@ -34,6 +34,7 @@ #include <pulsecore/native-common.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "simple.h" @@ -64,7 +65,7 @@ if (!(expression)) { \ 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) { \ @@ -81,8 +82,8 @@ if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ static void context_state_cb(pa_context *c, void *userdata) { pa_simple *p = userdata; - assert(c); - assert(p); + pa_assert(c); + pa_assert(p); switch (pa_context_get_state(c)) { case PA_CONTEXT_READY: @@ -101,8 +102,8 @@ static void context_state_cb(pa_context *c, void *userdata) { static void stream_state_cb(pa_stream *s, void * userdata) { pa_simple *p = userdata; - assert(s); - assert(p); + pa_assert(s); + pa_assert(p); switch (pa_stream_get_state(s)) { @@ -120,7 +121,7 @@ static void stream_state_cb(pa_stream *s, void * userdata) { static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { pa_simple *p = userdata; - assert(p); + pa_assert(p); pa_threaded_mainloop_signal(p->mainloop, 0); } @@ -128,22 +129,22 @@ static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { static void stream_latency_update_cb(pa_stream *s, void *userdata) { pa_simple *p = userdata; - assert(p); + 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) { - + 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; @@ -162,12 +163,12 @@ pa_simple* pa_simple_new( 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; @@ -180,7 +181,7 @@ pa_simple* pa_simple_new( /* 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; @@ -216,12 +217,12 @@ pa_simple* pa_simple_new( } pa_threaded_mainloop_unlock(p->mainloop); - + return p; unlock_and_fail: pa_threaded_mainloop_unlock(p->mainloop); - + fail: if (rerror) *rerror = error; @@ -230,14 +231,14 @@ fail: } void pa_simple_free(pa_simple *s) { - assert(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); @@ -248,60 +249,60 @@ void pa_simple_free(pa_simple *s) { } int pa_simple_write(pa_simple *p, const void*data, size_t length, int *rerror) { - assert(p); - + 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) { - assert(p); + 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); @@ -311,31 +312,31 @@ int pa_simple_read(pa_simple *p, void*data, size_t length, int *rerror) { } 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; @@ -344,8 +345,8 @@ unlock_and_fail: static void success_cb(pa_stream *s, int success, void *userdata) { pa_simple *p = userdata; - assert(s); - assert(p); + pa_assert(s); + pa_assert(p); p->operation_success = success; pa_threaded_mainloop_signal(p->mainloop, 0); @@ -353,8 +354,8 @@ static void success_cb(pa_stream *s, int success, void *userdata) { int pa_simple_drain(pa_simple *p, int *rerror) { pa_operation *o = NULL; - - assert(p); + + pa_assert(p); CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); @@ -370,7 +371,7 @@ int pa_simple_drain(pa_simple *p, int *rerror) { 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); @@ -389,8 +390,8 @@ unlock_and_fail: int pa_simple_flush(pa_simple *p, int *rerror) { pa_operation *o = NULL; - - assert(p); + + pa_assert(p); CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); @@ -399,7 +400,7 @@ int pa_simple_flush(pa_simple *p, int *rerror) { 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); @@ -426,14 +427,14 @@ unlock_and_fail: pa_usec_t pa_simple_get_latency(pa_simple *p, int *rerror) { pa_usec_t t; int negative; - - assert(p); - + + 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; @@ -442,7 +443,7 @@ pa_usec_t pa_simple_get_latency(pa_simple *p, int *rerror) { /* Wait until latency data is available again */ pa_threaded_mainloop_wait(p->mainloop); } - + pa_threaded_mainloop_unlock(p->mainloop); return negative ? 0 : t; diff --git a/src/pulse/simple.h b/src/pulse/simple.h index 0438d319..a1380a0a 100644 --- a/src/pulse/simple.h +++ b/src/pulse/simple.h @@ -1,21 +1,22 @@ #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 @@ -48,7 +49,7 @@ * pa_simple *s; * pa_sample_spec ss; * - * ss.format = PA_SAMPLE_S16_NE; + * ss.format = PA_SAMPLE_S16NE; * ss.channels = 2; * ss.rate = 44100; * @@ -127,18 +128,18 @@ pa_simple* pa_simple_new( 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 length, int *error); +int pa_simple_write(pa_simple *s, const void*data, size_t bytes, int *error); /** Wait until all data already written is played by the daemon */ 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 length, int *error); +int pa_simple_read(pa_simple *s, void*data, size_t bytes, int *error); -/** Return the playback latency. \since 0.5 */ +/** Return the playback latency. */ pa_usec_t pa_simple_get_latency(pa_simple *s, int *error); -/** Flush the playback buffer. \since 0.5 */ +/** Flush the playback buffer. */ int pa_simple_flush(pa_simple *s, int *error); PA_C_DECL_END diff --git a/src/pulse/stream.c b/src/pulse/stream.c index d31127d8..3bee7a05 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <string.h> #include <stdio.h> #include <string.h> @@ -35,25 +35,22 @@ #include <pulsecore/pstream-util.h> #include <pulsecore/log.h> #include <pulsecore/hashmap.h> +#include <pulsecore/macro.h> +#include <pulsecore/rtclock.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; - - assert(c); +#define LATENCY_IPOL_INTERVAL_USEC (333*PA_USEC_PER_MSEC) - PA_CHECK_VALIDITY_RETURN_NULL(c, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID); - PA_CHECK_VALIDITY_RETURN_NULL(c, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID); +#define SMOOTHER_ADJUST_TIME (1000*PA_USEC_PER_MSEC) +#define SMOOTHER_HISTORY_TIME (5000*PA_USEC_PER_MSEC) +#define SMOOTHER_MIN_HISTORY (4) - s = pa_xnew(pa_stream, 1); - s->ref = 1; - s->context = c; - s->mainloop = c->mainloop; +pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map) { + return pa_stream_new_with_proplist(c, name, ss, map, NULL); +} +static void reset_callbacks(pa_stream *s) { s->read_callback = NULL; s->read_userdata = NULL; s->write_callback = NULL; @@ -66,47 +63,90 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec * s->underflow_userdata = NULL; s->latency_update_callback = NULL; s->latency_update_userdata = NULL; + s->moved_callback = NULL; + s->moved_userdata = NULL; + s->suspended_callback = NULL; + s->suspended_userdata = NULL; + s->started_callback = NULL; + s->started_userdata = NULL; +} + +pa_stream *pa_stream_new_with_proplist( + pa_context *c, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_proplist *p) { + + pa_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); + PA_CHECK_VALIDITY_RETURN_NULL(c, name || (p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), 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->direction = PA_STREAM_NODIRECTION; - s->name = pa_xstrdup(name); - s->sample_spec = *ss; + s->state = PA_STREAM_UNCONNECTED; s->flags = 0; - if (map) - s->channel_map = *map; - else - pa_channel_map_init_auto(&s->channel_map, ss->channels, PA_CHANNEL_MAP_DEFAULT); - + s->sample_spec = *ss; + s->channel_map = *map; + + s->direct_on_input = PA_INVALID_INDEX; + + s->proplist = p ? pa_proplist_copy(p) : pa_proplist_new(); + if (name) + pa_proplist_sets(s->proplist, PA_PROP_MEDIA_NAME, name); + s->channel = 0; - s->channel_valid = 0; + s->channel_valid = FALSE; s->syncid = c->csyncid++; - s->device_index = PA_INVALID_INDEX; + s->stream_index = PA_INVALID_INDEX; + s->requested_bytes = 0; - s->state = PA_STREAM_UNCONNECTED; memset(&s->buffer_attr, 0, sizeof(s->buffer_attr)); - s->peek_memchunk.index = 0; - s->peek_memchunk.length = 0; - s->peek_memchunk.memblock = NULL; + s->device_index = PA_INVALID_INDEX; + s->device_name = NULL; + s->suspended = FALSE; + + pa_memchunk_reset(&s->peek_memchunk); s->peek_data = NULL; s->record_memblockq = NULL; + s->corked = FALSE; + + memset(&s->timing_info, 0, sizeof(s->timing_info)); + s->timing_info_valid = FALSE; + 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; + s->auto_timing_update_requested = FALSE; + + reset_callbacks(s); + + s->smoother = NULL; /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */ PA_LLIST_PREPEND(pa_stream, c->streams, s); @@ -115,14 +155,51 @@ pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec * return s; } -static void stream_free(pa_stream *s) { - assert(s && !s->context && !s->channel_valid); +static void stream_unlink(pa_stream *s) { + pa_operation *o, *n; + pa_assert(s); + + if (!s->context) + return; + + /* Detach from context */ + + /* 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); + s->channel = 0; + s->channel_valid = FALSE; + } + + PA_LLIST_REMOVE(pa_stream, s->context->streams, s); + pa_stream_unref(s); + + s->context = NULL; if (s->auto_timing_update_event) { - assert(s->mainloop); + pa_assert(s->mainloop); s->mainloop->time_free(s->auto_timing_update_event); } + reset_callbacks(s); +} + +static void stream_free(pa_stream *s) { + pa_assert(s); + + stream_unlink(s); + if (s->peek_memchunk.memblock) { if (s->peek_data) pa_memblock_release(s->peek_memchunk.memblock); @@ -132,115 +209,127 @@ static void stream_free(pa_stream *s) { if (s->record_memblockq) pa_memblockq_free(s->record_memblockq); - pa_xfree(s->name); + if (s->proplist) + pa_proplist_free(s->proplist); + + if (s->smoother) + pa_smoother_free(s->smoother); + + pa_xfree(s->device_name); pa_xfree(s); } void pa_stream_unref(pa_stream *s) { - assert(s); - assert(s->ref >= 1); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); - if (--(s->ref) == 0) + if (PA_REFCNT_DEC(s) <= 0) stream_free(s); } pa_stream* pa_stream_ref(pa_stream *s) { - assert(s); - assert(s->ref >= 1); - - s->ref++; + 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) { - assert(s); - assert(s->ref >= 1); - + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + return s->state; } pa_context* pa_stream_get_context(pa_stream *s) { - assert(s); - assert(s->ref >= 1); - + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + return s->context; } uint32_t pa_stream_get_index(pa_stream *s) { - assert(s); - assert(s->ref >= 1); + 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->device_index; + + return s->stream_index; } - + void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) { - assert(s); - assert(s->ref >= 1); + 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) { + if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED)) + stream_unlink(s); - /* Detach from context */ - pa_operation *o, *n; + pa_stream_unref(s); +} - /* 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); +static void request_auto_timing_update(pa_stream *s, pa_bool_t force) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); - 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); + if (!(s->flags & PA_STREAM_AUTO_TIMING_UPDATE)) + return; - s->channel = 0; - s->channel_valid = 0; - - s->context = NULL; + 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 = TRUE; + } } - pa_stream_unref(s); + 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); + } } 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; - - assert(pd); - assert(command == PA_COMMAND_PLAYBACK_STREAM_KILLED || command == PA_COMMAND_RECORD_STREAM_KILLED); - assert(t); - assert(c); + + 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; + if (s->state != PA_STREAM_READY) + goto finish; + pa_context_set_error(c, PA_ERR_KILLED); pa_stream_set_state(s, PA_STREAM_FAILED); @@ -248,34 +337,226 @@ 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; + pa_bool_t suspended; + uint32_t di; + pa_usec_t usec; + uint32_t maxlength = 0, fragsize = 0, minreq = 0, tlength = 0, prebuf = 0; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_MOVED || command == PA_COMMAND_RECORD_STREAM_MOVED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 12) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_getu32(t, &di) < 0 || + pa_tagstruct_gets(t, &dn) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->version >= 13) { + + if (command == PA_COMMAND_RECORD_STREAM_MOVED) { + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &fragsize) < 0 || + pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + } else { + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &tlength) < 0 || + pa_tagstruct_getu32(t, &prebuf) < 0 || + pa_tagstruct_getu32(t, &minreq) < 0 || + pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + } + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!dn || di == PA_INVALID_INDEX) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_MOVED ? c->playback_streams : c->record_streams, channel))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + if (c->version >= 13) { + if (s->direction == PA_STREAM_RECORD) + s->timing_info.configured_source_usec = usec; + else + s->timing_info.configured_sink_usec = usec; + + s->buffer_attr.maxlength = maxlength; + s->buffer_attr.fragsize = fragsize; + s->buffer_attr.tlength = tlength; + s->buffer_attr.prebuf = prebuf; + s->buffer_attr.minreq = minreq; + } + + pa_xfree(s->device_name); + s->device_name = pa_xstrdup(dn); + s->device_index = di; + + s->suspended = suspended; + + request_auto_timing_update(s, TRUE); + + if (s->moved_callback) + s->moved_callback(s, s->moved_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_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; + pa_bool_t 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; + + if (s->state != PA_STREAM_READY) + goto finish; + + s->suspended = suspended; + + if (s->smoother) { + pa_usec_t x = pa_rtclock_usec(); + + if (s->timing_info_valid) + x -= s->timing_info.transport_usec; + + if (s->suspended || s->corked) + pa_smoother_pause(s->smoother, x); + else + pa_smoother_resume(s->smoother, x); + } + + request_auto_timing_update(s, TRUE); + + if (s->suspended_callback) + s->suspended_callback(s, s->suspended_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_STARTED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 13) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_dynarray_get(c->playback_streams, channel))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + request_auto_timing_update(s, TRUE); + + if (s->started_callback) + s->started_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; - - assert(pd); - assert(command == PA_COMMAND_REQUEST); - assert(t); - assert(c); + + 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); - } + if (s->state != PA_STREAM_READY) + goto finish; + + 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); @@ -286,22 +567,38 @@ void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, PA_GCC pa_context *c = userdata; uint32_t channel; - assert(pd); - assert(command == PA_COMMAND_OVERFLOW || command == PA_COMMAND_UNDERFLOW); - assert(t); - assert(c); + 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) + goto finish; + + if (s->smoother) + if (s->direction == PA_STREAM_PLAYBACK && s->buffer_attr.prebuf > 0) { + pa_usec_t x = pa_rtclock_usec(); + + if (s->timing_info_valid) + x -= s->timing_info.transport_usec; + + pa_smoother_pause(s->smoother, x); + } + + request_auto_timing_update(s, TRUE); + if (s->state == PA_STREAM_READY) { if (command == PA_COMMAND_OVERFLOW) { @@ -317,98 +614,122 @@ void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, PA_GCC pa_context_unref(c); } -static void request_auto_timing_update(pa_stream *s, int force) { - struct timeval next; - assert(s); - - 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; - } - } - - 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) { - assert(s); +static void invalidate_indexes(pa_stream *s, pa_bool_t r, pa_bool_t 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; - + s->timing_info.write_index_corrupt = TRUE; + /* 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; - + s->timing_info.read_index_corrupt = TRUE; + /* 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); + + request_auto_timing_update(s, TRUE); } 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_log("time event"); */ + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); pa_stream_ref(s); - request_auto_timing_update(s, 0); + request_auto_timing_update(s, FALSE); pa_stream_unref(s); } +static void create_stream_complete(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->state == PA_STREAM_CREATING); + + pa_stream_set_state(s, PA_STREAM_READY); + + if (s->requested_bytes > 0 && s->write_callback) + s->write_callback(s, 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); + + request_auto_timing_update(s, TRUE); + } +} + +static void automatic_buffer_attr(pa_stream *s, pa_buffer_attr *attr, const pa_sample_spec *ss) { + pa_assert(s); + pa_assert(attr); + pa_assert(ss); + + if (s->context->version >= 13) + return; + + /* Version older than 0.9.10 didn't do server side buffer_attr + * selection, hence we have to fake it on the client side */ + + if (!attr->maxlength <= 0) + attr->maxlength = 4*1024*1024; /* 4MB is the maximum queue length PulseAudio <= 0.9.9 supported. */ + + if (!attr->tlength <= 0) + attr->tlength = pa_bytes_per_second(ss)*2; /* 2s of buffering */ + + if (!attr->minreq <= 0) + attr->minreq = (2*attr->tlength)/10; /* Ask for more data when there are only 200ms left in the playback buffer */ + + if (!attr->prebuf) + attr->prebuf = attr->tlength; /* Start to play only when the playback is fully filled up once */ + + if (!attr->fragsize) + attr->fragsize = attr->tlength; /* Pass data to the app only when the buffer is filled up once */ +} + void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { pa_stream *s = userdata; - - assert(pd); - assert(s); - assert(s->state == PA_STREAM_CREATING); - + + 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) + if (pa_context_handle_error(s->context, command, t, FALSE) < 0) goto finish; - + pa_stream_set_state(s, PA_STREAM_FAILED); goto finish; } if (pa_tagstruct_getu32(t, &s->channel) < 0 || - ((s->direction != PA_STREAM_UPLOAD) && pa_tagstruct_getu32(t, &s->device_index) < 0) || + s->channel == PA_INVALID_INDEX || + ((s->direction != PA_STREAM_UPLOAD) && (pa_tagstruct_getu32(t, &s->stream_index) < 0 || s->stream_index == PA_INVALID_INDEX)) || ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &s->requested_bytes) < 0)) { pa_context_fail(s->context, PA_ERR_PROTOCOL); goto finish; } - if (pa_context_get_server_protocol_version(s->context) >= 9) { + 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 || @@ -426,44 +747,77 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED } } + if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) { + pa_sample_spec ss; + pa_channel_map cm; + const char *dn = NULL; + pa_bool_t 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; + + s->channel_map = cm; + s->sample_spec = ss; + } + + if (s->context->version >= 13 && s->direction != PA_STREAM_UPLOAD) { + pa_usec_t usec; + + if (pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (s->direction == PA_STREAM_RECORD) + s->timing_info.configured_source_usec = usec; + else + s->timing_info.configured_sink_usec = usec; + } + if (!pa_tagstruct_eof(t)) { pa_context_fail(s->context, PA_ERR_PROTOCOL); goto finish; } if (s->direction == PA_STREAM_RECORD) { - assert(!s->record_memblockq); - + pa_assert(!s->record_memblockq); + s->record_memblockq = pa_memblockq_new( - 0, + 0, s->buffer_attr.maxlength, 0, pa_frame_size(&s->sample_spec), 1, 0, + 0, NULL); } - s->channel_valid = 1; + s->channel_valid = TRUE; pa_dynarray_put((s->direction == PA_STREAM_RECORD) ? s->context->record_streams : s->context->playback_streams, s->channel, s); - pa_stream_set_state(s, PA_STREAM_READY); - - if (s->direction != PA_STREAM_UPLOAD && - s->flags & PA_STREAM_AUTO_TIMING_UPDATE) { - struct timeval tv; - - pa_gettimeofday(&tv); - tv.tv_usec += LATENCY_IPOL_INTERVAL_USEC; /* every 100 ms */ - - assert(!s->auto_timing_update_event); - s->auto_timing_update_event = s->mainloop->time_new(s->mainloop, &tv, &auto_timing_update_callback, s); - - request_auto_timing_update(s, 1); - } - - if (s->requested_bytes > 0 && s->ref > 1 && s->write_callback) - s->write_callback(s, s->requested_bytes, s->write_userdata); + create_stream_complete(s); finish: pa_stream_unref(s); @@ -477,19 +831,40 @@ static int create_stream( pa_stream_flags_t flags, const pa_cvolume *volume, pa_stream *sync_stream) { - + pa_tagstruct *t; uint32_t tag; - - assert(s); - assert(s->ref >= 1); - + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(direction == PA_STREAM_PLAYBACK || direction == PA_STREAM_RECORD); + PA_CHECK_VALIDITY(s->context, 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 : 0))), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->direct_on_input == PA_INVALID_INDEX || direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, !(flags & ~(PA_STREAM_START_CORKED| + PA_STREAM_INTERPOLATE_TIMING| + PA_STREAM_NOT_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| + PA_STREAM_VARIABLE_RATE| + PA_STREAM_PEAK_DETECT| + PA_STREAM_START_MUTED| + PA_STREAM_ADJUST_LATENCY)), PA_ERR_INVALID); + + PA_CHECK_VALIDITY(s->context, s->context->version >= 12 || !(flags & PA_STREAM_VARIABLE_RATE), PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY(s->context, s->context->version >= 13 || !(flags & PA_STREAM_PEAK_DETECT), PA_ERR_NOTSUPPORTED); + /* Althought some of the other flags are not supported on older + * version, we don't check for them here, because it doesn't hurt + * when they are passed but actually not supported. This makes + * client development easier */ + + PA_CHECK_VALIDITY(s->context, direction == PA_STREAM_PLAYBACK || !(flags & (PA_STREAM_START_MUTED)), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, direction == PA_STREAM_RECORD || !(flags & (PA_STREAM_PEAK_DETECT)), 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); @@ -497,19 +872,25 @@ static int create_stream( s->direction = direction; s->flags = flags; - + if (sync_stream) s->syncid = sync_stream->syncid; - + if (attr) s->buffer_attr = *attr; - else { - /* half a second */ - 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/100; - s->buffer_attr.prebuf = s->buffer_attr.tlength - s->buffer_attr.minreq; - s->buffer_attr.fragsize = s->buffer_attr.tlength/100; + automatic_buffer_attr(s, &s->buffer_attr, &s->sample_spec); + + if (flags & PA_STREAM_INTERPOLATE_TIMING) { + pa_usec_t x; + + if (s->smoother) + pa_smoother_free(s->smoother); + + s->smoother = pa_smoother_new(SMOOTHER_ADJUST_TIME, SMOOTHER_HISTORY_TIME, !(flags & PA_STREAM_NOT_MONOTONOUS), SMOOTHER_MIN_HISTORY); + + x = pa_rtclock_usec(); + pa_smoother_set_time_offset(s->smoother, x); + pa_smoother_pause(s->smoother, x); } if (!dev) @@ -519,10 +900,12 @@ static int create_stream( s->context, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CREATE_PLAYBACK_STREAM : PA_COMMAND_CREATE_RECORD_STREAM, &tag); - + + if (s->context->version < 13) + pa_tagstruct_puts(t, pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME)); + pa_tagstruct_put( t, - PA_TAG_STRING, s->name, PA_TAG_SAMPLE_SPEC, &s->sample_spec, PA_TAG_CHANNEL_MAP, &s->channel_map, PA_TAG_U32, PA_INVALID_INDEX, @@ -530,7 +913,7 @@ static int create_stream( 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; @@ -544,16 +927,46 @@ static int create_stream( 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) { + pa_tagstruct_put( + t, + PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMAP_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMIX_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_FORMAT, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_RATE, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_DONT_MOVE, + PA_TAG_BOOLEAN, flags & PA_STREAM_VARIABLE_RATE, + PA_TAG_INVALID); + } + + if (s->context->version >= 13) { + + if (s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & PA_STREAM_START_MUTED); + else + pa_tagstruct_put_boolean(t, flags & PA_STREAM_PEAK_DETECT); + + pa_tagstruct_put( + t, + PA_TAG_BOOLEAN, flags & PA_STREAM_ADJUST_LATENCY, + PA_TAG_PROPLIST, s->proplist, + PA_TAG_INVALID); + + if (s->direction == PA_STREAM_RECORD) + pa_tagstruct_putu32(t, s->direct_on_input); + } + 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; } @@ -565,10 +978,10 @@ int pa_stream_connect_playback( pa_stream_flags_t flags, pa_cvolume *volume, pa_stream *sync_stream) { - - assert(s); - assert(s->ref >= 1); - + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + return create_stream(PA_STREAM_PLAYBACK, s, dev, attr, flags, volume, sync_stream); } @@ -577,9 +990,9 @@ int pa_stream_connect_record( const char *dev, const pa_buffer_attr *attr, pa_stream_flags_t flags) { - - assert(s); - assert(s->ref >= 1); + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL); } @@ -591,12 +1004,16 @@ int pa_stream_write( void (*free_cb)(void *p), int64_t offset, pa_seek_mode_t seek) { - + pa_memchunk chunk; - - assert(s); - assert(s->ref >= 1); - assert(data); + pa_seek_mode_t t_seek; + int64_t t_offset; + size_t t_length; + const void *t_data; + + 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); @@ -606,72 +1023,93 @@ int pa_stream_write( 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); + t_seek = seek; + t_offset = offset; + t_length = length; + t_data = data; + + while (t_length > 0) { + + chunk.index = 0; + + if (free_cb && !pa_pstream_get_shm(s->context->pstream)) { + chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) t_data, t_length, free_cb, 1); + chunk.length = t_length; + } else { + void *d; + + chunk.length = PA_MIN(t_length, pa_mempool_block_size_max(s->context->mempool)); + chunk.memblock = pa_memblock_new(s->context->mempool, chunk.length); + + d = pa_memblock_acquire(chunk.memblock); + memcpy(d, t_data, chunk.length); + pa_memblock_release(chunk.memblock); + } + + pa_pstream_send_memblock(s->context->pstream, s->channel, t_offset, t_seek, &chunk); + + t_offset = 0; + t_seek = PA_SEEK_RELATIVE; + + t_data = (const uint8_t*) t_data + chunk.length; + t_length -= chunk.length; + + pa_memblock_unref(chunk.memblock); } - - chunk.index = 0; - chunk.length = length; - pa_pstream_send_memblock(s->context->pstream, s->channel, offset, seek, &chunk); - pa_memblock_unref(chunk.memblock); - + if (free_cb && pa_pstream_get_shm(s->context->pstream)) + free_cb((void*) data); + 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].corrupt = FALSE; + s->write_index_corrections[s->current_write_index_correction].absolute = TRUE; 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; + s->write_index_corrections[s->current_write_index_correction].corrupt = TRUE; } - + /* Update the write index in the already available latency data */ if (s->timing_info_valid) { - + if (seek == PA_SEEK_ABSOLUTE) { - s->timing_info.write_index_corrupt = 0; + s->timing_info.write_index_corrupt = FALSE; 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; + s->timing_info.write_index_corrupt = TRUE; } if (!s->timing_info_valid || s->timing_info.write_index_corrupt) - request_auto_timing_update(s, 1); + request_auto_timing_update(s, TRUE); } - + return 0; } int pa_stream_peek(pa_stream *s, const void **data, size_t *length) { - assert(s); - assert(s->ref >= 1); - assert(data); - assert(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) { @@ -683,49 +1121,47 @@ int pa_stream_peek(pa_stream *s, const void **data, size_t *length) { s->peek_data = pa_memblock_acquire(s->peek_memchunk.memblock); } - assert(s->peek_data); + 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) { - assert(s); - assert(s->ref >= 1); + 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, s->peek_memchunk.length); + + 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; - assert(s->peek_data); + 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; + pa_memchunk_reset(&s->peek_memchunk); return 0; } size_t pa_stream_writable_size(pa_stream *s) { - assert(s); - assert(s->ref >= 1); + 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) { - assert(s); - assert(s->ref >= 1); + 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); @@ -737,9 +1173,9 @@ pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *us pa_operation *o; pa_tagstruct *t; uint32_t tag; - - assert(s); - assert(s->ref >= 1); + + 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); @@ -754,13 +1190,75 @@ pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *us return o; } +static pa_usec_t calc_time(pa_stream *s, pa_bool_t ignore_transport) { + pa_usec_t usec; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->state == PA_STREAM_READY); + pa_assert(s->direction != PA_STREAM_UPLOAD); + pa_assert(s->timing_info_valid); + pa_assert(s->direction != PA_STREAM_PLAYBACK || !s->timing_info.read_index_corrupt); + pa_assert(s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt); + + if (s->direction == PA_STREAM_PLAYBACK) { + /* The last byte that was written into the output device + * had this time value associated */ + usec = pa_bytes_to_usec(s->timing_info.read_index < 0 ? 0 : (uint64_t) s->timing_info.read_index, &s->sample_spec); + + if (!s->corked && !s->suspended) { + + if (!ignore_transport) + /* Because the latency info took a little time to come + * to us, we assume that the real output time is actually + * a little ahead */ + usec += s->timing_info.transport_usec; + + /* However, the output device usually maintains a buffer + too, hence the real sample currently played is a little + back */ + if (s->timing_info.sink_usec >= usec) + usec = 0; + else + usec -= s->timing_info.sink_usec; + } + + } else 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 && !s->suspended) { + + if (!ignore_transport) + /* Add transport latency */ + usec += s->timing_info.transport_usec; + + /* Add latency of data in device buffer */ + usec += s->timing_info.source_usec; + + /* If this is a monitor source, we need to correct the + * time by the playback device buffer */ + if (s->timing_info.sink_usec >= usec) + usec = 0; + else + usec -= s->timing_info.sink_usec; + } + } + + return usec; +} + static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_operation *o = userdata; struct timeval local, remote, now; pa_timing_info *i; - - assert(pd); - assert(o); + pa_bool_t playing = FALSE; + uint64_t underrun_for = 0, playing_for = 0; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); if (!o->context || !o->stream) goto finish; @@ -768,58 +1266,77 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, 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; + + o->stream->timing_info_valid = FALSE; + i->write_index_corrupt = FALSE; + i->read_index_corrupt = FALSE; /* pa_log("timing update %u\n", tag); */ if (command != PA_COMMAND_REPLY) { - if (pa_context_handle_error(o->context, command, t) < 0) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; - } else if (pa_tagstruct_get_usec(t, &i->sink_usec) < 0 || - pa_tagstruct_get_usec(t, &i->source_usec) < 0 || - pa_tagstruct_get_boolean(t, &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; + + if (pa_tagstruct_get_usec(t, &i->sink_usec) < 0 || + pa_tagstruct_get_usec(t, &i->source_usec) < 0 || + pa_tagstruct_get_boolean(t, &playing) < 0 || + pa_tagstruct_get_timeval(t, &local) < 0 || + pa_tagstruct_get_timeval(t, &remote) < 0 || + pa_tagstruct_gets64(t, &i->write_index) < 0 || + pa_tagstruct_gets64(t, &i->read_index) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->context->version >= 13 && + o->stream->direction == PA_STREAM_PLAYBACK) + if (pa_tagstruct_getu64(t, &underrun_for) < 0 || + pa_tagstruct_getu64(t, &playing_for) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + o->stream->timing_info_valid = TRUE; + i->playing = (int) playing; + i->since_underrun = playing ? playing_for : underrun_for; 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->synchronized_clocks = TRUE; i->timestamp = remote; } else { /* clocks are not synchronized, let's estimate latency then */ i->transport_usec = pa_timeval_diff(&now, &local)/2; - i->synchronized_clocks = 0; + i->synchronized_clocks = FALSE; i->timestamp = local; pa_timeval_add(&i->timestamp, i->transport_usec); } /* Invalidate read and write indexes if necessary */ if (tag < o->stream->read_index_not_before) - i->read_index_corrupt = 1; + i->read_index_corrupt = TRUE; if (tag < o->stream->write_index_not_before) - i->write_index_corrupt = 1; + i->write_index_corrupt = TRUE; if (o->stream->direction == PA_STREAM_PLAYBACK) { /* Write index correction */ @@ -828,7 +1345,7 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, 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) { @@ -845,11 +1362,11 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, if (o->stream->write_index_corrections[j].corrupt) { /* A corrupting seek was made */ i->write_index = 0; - i->write_index_corrupt = 1; + i->write_index_corrupt = TRUE; } else if (o->stream->write_index_corrections[j].absolute) { /* An absolute seek was made */ i->write_index = o->stream->write_index_corrections[j].value; - i->write_index_corrupt = 0; + i->write_index_corrupt = FALSE; } else if (!i->write_index_corrupt) { /* A relative seek was made */ i->write_index += o->stream->write_index_corrections[j].value; @@ -863,34 +1380,66 @@ static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, 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; + + /* 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 = FALSE; + } + } + + /* Update smoother */ + if (o->stream->smoother) { + pa_usec_t u, x; + + u = x = pa_rtclock_usec() - i->transport_usec; + + if (o->stream->direction == PA_STREAM_PLAYBACK && + o->context->version >= 13) { + pa_usec_t su; + + /* If we weren't playing then it will take some time + * until the audio will actually come out through the + * speakers. Since we follow that timing here, we need + * to try to fix this up */ + + su = pa_bytes_to_usec(i->since_underrun, &o->stream->sample_spec); + + if (su < i->sink_usec) + x += i->sink_usec - su; + } + + if (!i->playing) + pa_smoother_pause(o->stream->smoother, x); + + /* Update the smoother */ + if ((o->stream->direction == PA_STREAM_PLAYBACK && !i->read_index_corrupt) || + (o->stream->direction == PA_STREAM_RECORD && !i->write_index_corrupt)) + pa_smoother_put(o->stream->smoother, u, calc_time(o->stream, TRUE)); + + if (i->playing) + pa_smoother_resume(o->stream->smoother, x); } } + o->stream->auto_timing_update_requested = FALSE; + if (o->stream->latency_update_callback) o->stream->latency_update_callback(o->stream, o->stream->latency_update_userdata); - + if (o->callback && o->stream && o->stream->state == PA_STREAM_READY) { pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; cb(o->stream, o->stream->timing_info_valid, o->userdata); } - + finish: pa_operation_done(o); @@ -903,58 +1452,58 @@ pa_operation* pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t pa_tagstruct *t; struct timeval now; int cidx = 0; - - assert(s); - assert(s->ref >= 1); + + 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; + + s->current_write_index_correction = cidx; + + s->write_index_corrections[cidx].valid = TRUE; + s->write_index_corrections[cidx].absolute = FALSE; + s->write_index_corrections[cidx].corrupt = FALSE; + s->write_index_corrections[cidx].tag = tag; + s->write_index_corrections[cidx].value = 0; } - -/* 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; - - assert(pd); - assert(s); - assert(s->ref >= 1); + + 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) + if (pa_context_handle_error(s->context, command, t, FALSE) < 0) goto finish; pa_stream_set_state(s, PA_STREAM_FAILED); @@ -973,9 +1522,9 @@ finish: int pa_stream_disconnect(pa_stream *s) { pa_tagstruct *t; uint32_t tag; - - assert(s); - assert(s->ref >= 1); + + 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); @@ -996,66 +1545,117 @@ int pa_stream_disconnect(pa_stream *s) { } void pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) { - assert(s); - assert(s->ref >= 1); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; s->read_callback = cb; s->read_userdata = userdata; } void pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) { - assert(s); - assert(s->ref >= 1); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; s->write_callback = cb; s->write_userdata = userdata; } void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { - assert(s); - assert(s->ref >= 1); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; s->state_callback = cb; s->state_userdata = userdata; } void pa_stream_set_overflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { - assert(s); - assert(s->ref >= 1); - + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + s->overflow_callback = cb; s->overflow_userdata = userdata; } void pa_stream_set_underflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { - assert(s); - assert(s->ref >= 1); - + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + s->underflow_callback = cb; s->underflow_userdata = userdata; } void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { - assert(s); - assert(s->ref >= 1); - + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + s->latency_update_callback = cb; s->latency_update_userdata = userdata; } +void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->moved_callback = cb; + s->moved_userdata = userdata; +} + +void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->suspended_callback = cb; + s->suspended_userdata = userdata; +} + +void pa_stream_set_started_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->started_callback = cb; + s->started_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; - - assert(pd); - assert(o); - assert(o->ref >= 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) + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; success = 0; @@ -1078,15 +1678,15 @@ pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, voi pa_operation *o; pa_tagstruct *t; uint32_t tag; - - assert(s); - assert(s->ref >= 1); - + + 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( @@ -1098,8 +1698,18 @@ pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, voi 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->smoother) { + pa_usec_t x = pa_rtclock_usec(); + + if (s->timing_info_valid) + x += s->timing_info.transport_usec; + + if (s->suspended || s->corked) + pa_smoother_pause(s->smoother, x); + } + if (s->direction == PA_STREAM_PLAYBACK) - invalidate_indexes(s, 1, 0); + invalidate_indexes(s, TRUE, FALSE); return o; } @@ -1108,12 +1718,12 @@ static pa_operation* stream_send_simple_command(pa_stream *s, uint32_t command, pa_tagstruct *t; pa_operation *o; uint32_t tag; - - assert(s); - assert(s->ref >= 1); + + 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); @@ -1126,84 +1736,114 @@ static pa_operation* stream_send_simple_command(pa_stream *s, uint32_t command, 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->state == PA_STREAM_READY, PA_ERR_BADSTATE); 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; - + s->write_index_corrections[s->current_write_index_correction].corrupt = TRUE; + if (s->timing_info_valid) - s->timing_info.write_index_corrupt = 1; + s->timing_info.write_index_corrupt = TRUE; - if (s->buffer_attr.prebuf > 0) - invalidate_indexes(s, 1, 0); + if (s->buffer_attr.prebuf > 0) + invalidate_indexes(s, TRUE, FALSE); else - request_auto_timing_update(s, 1); + request_auto_timing_update(s, TRUE); + + if (s->smoother && s->buffer_attr.prebuf > 0) { + pa_usec_t x = pa_rtclock_usec(); + + if (s->timing_info_valid) + x += s->timing_info.transport_usec; + + pa_smoother_pause(s->smoother, x); + } + } else - invalidate_indexes(s, 0, 1); + invalidate_indexes(s, FALSE, TRUE); } - + 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->state == PA_STREAM_READY, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); if ((o = stream_send_simple_command(s, PA_COMMAND_PREBUF_PLAYBACK_STREAM, cb, userdata))) - invalidate_indexes(s, 1, 0); + invalidate_indexes(s, TRUE, FALSE); 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->state == PA_STREAM_READY, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); if ((o = stream_send_simple_command(s, PA_COMMAND_TRIGGER_PLAYBACK_STREAM, cb, userdata))) - invalidate_indexes(s, 1, 0); - + invalidate_indexes(s, TRUE, FALSE); + 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; - - assert(s); - assert(s->ref >= 1); - assert(name); + + 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); + if (s->context->version >= 13) { + pa_proplist *p = pa_proplist_new(); - 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); + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name); + o = pa_stream_proplist_update(s, PA_UPDATE_REPLACE, p, cb, userdata); + pa_proplist_free(p); + } else { + pa_tagstruct *t; + uint32_t tag; + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + t = pa_tagstruct_command( + s->context, + 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_usec_t usec; - assert(s); - assert(s->ref >= 1); + 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); @@ -1211,65 +1851,10 @@ int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) { 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); - } - } + if (s->smoother) + usec = pa_smoother_get(s->smoother, pa_rtclock_usec()); + else + usec = calc_time(s, FALSE); /* Make sure the time runs monotonically */ if (!(s->flags & PA_STREAM_NOT_MONOTONOUS)) { @@ -1281,18 +1866,18 @@ int pa_stream_get_time(pa_stream *s, pa_usec_t *r_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) { - assert(s); - assert(s->ref >= 1); - + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + if (negative) *negative = 0; - if (a >= b) + if (a >= b) return a-b; else { if (negative && s->direction == PA_STREAM_RECORD) { @@ -1308,16 +1893,16 @@ int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative) { int r; int64_t cindex; - assert(s); - assert(s->ref >= 1); - assert(r_usec); + 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; @@ -1328,7 +1913,7 @@ int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative) { if (cindex < 0) cindex = 0; - + c = pa_bytes_to_usec(cindex, &s->sample_spec); if (s->direction == PA_STREAM_PLAYBACK) @@ -1340,8 +1925,8 @@ int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative) { } const pa_timing_info* pa_stream_get_timing_info(pa_stream *s) { - assert(s); - assert(s->ref >= 1); + 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); @@ -1351,27 +1936,323 @@ const pa_timing_info* pa_stream_get_timing_info(pa_stream *s) { } const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s) { - assert(s); - assert(s->ref >= 1); + 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) { - assert(s); - assert(s->ref >= 1); + 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) { - assert(s); - assert(s->ref >= 1); + 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, - pa_context_get_server_protocol_version(s->context) >= 9, PA_ERR_NODATA); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 9, PA_ERR_NOTSUPPORTED); return &s->buffer_attr; } + +static void stream_set_buffer_attr_callback(pa_pdispatch *pd, uint32_t command, 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, FALSE) < 0) + goto finish; + + success = 0; + } else { + + if (o->stream->direction == PA_STREAM_PLAYBACK) { + if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.tlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.prebuf) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.minreq) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } else if (o->stream->direction == PA_STREAM_RECORD) { + if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.fragsize) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + + +pa_operation* pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_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); + + if (s->context->version >= 13) + pa_tagstruct_put_boolean(t, !!(s->flags & PA_STREAM_ADJUST_LATENCY)); + + 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; +} + +int pa_stream_is_corked(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); + + return s->corked; +} + +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, FALSE) < 0) + goto finish; + + success = 0; + } else { + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + o->stream->sample_spec.rate = PA_PTR_TO_UINT(o->private); + pa_assert(pa_sample_spec_valid(&o->stream->sample_spec)); + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + + +pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, 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; +} + +pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_RECORD ? PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST : PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST, + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_putu32(t, (uint32_t) mode); + pa_tagstruct_put_proplist(t, p); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update s->proplist here, because we + * don't export that field */ + + return o; +} + +pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + const char * const*k; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, keys && keys[0], PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_RECORD ? PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST : PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST, + &tag); + pa_tagstruct_putu32(t, s->channel); + + for (k = keys; *k; k++) + pa_tagstruct_puts(t, *k); + + pa_tagstruct_puts(t, NULL); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update s->proplist here, because we + * don't export that field */ + + return o; +} + +int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, sink_input_idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED); + + s->direct_on_input = sink_input_idx; + + return 0; +} + +uint32_t pa_stream_get_monitor_stream(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direct_on_input != PA_INVALID_INDEX, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX); + + return s->direct_on_input; +} diff --git a/src/pulse/stream.h b/src/pulse/stream.h index ad15125a..55f36b7f 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -1,21 +1,22 @@ #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 @@ -113,7 +114,7 @@ * 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 @@ -159,13 +160,13 @@ * \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 @@ -217,7 +218,7 @@ * 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 @@ -268,18 +269,30 @@ typedef struct pa_stream pa_stream; 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 length, void *userdata); +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 */ +/** Create a new, unconnected stream with the specified name and + * sample type. It is recommended to use pa_stream_new_with_proplist() + * instead and specify some initial properties. */ pa_stream* pa_stream_new( - pa_context *c /**< The context to create this stream in */, + pa_context *c /**< The context to create this stream in */, const char *name /**< A name for this stream */, const pa_sample_spec *ss /**< The desired sample format */, const pa_channel_map *map /**< The desired channel map, or NULL for default */); +/** Create a new, unconnected stream with the specified name and + * sample type, and specify the the initial stream property + * list. \since 0.9.11 */ +pa_stream* pa_stream_new_with_proplist( + pa_context *c /**< The context to create this stream in */, + const char *name /**< A name for this stream */, + const pa_sample_spec *ss /**< The desired sample format */, + const pa_channel_map *map /**< The desired channel map, or NULL for default */, + pa_proplist *p /**< The initial property list */); + /** Decrease the reference counter by one */ void pa_stream_unref(pa_stream *s); @@ -292,9 +305,42 @@ 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 device (sink input or source output) index this stream is connected to */ +/** 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); + +/** Return 1 if the this stream has been corked. This will return 0 if + * not, and negative on error. \since 0.9.11 */ +int pa_stream_is_corked(pa_stream *s); + /** Connect the stream to a sink */ int pa_stream_connect_playback( pa_stream *s /**< The stream to connect to a sink */, @@ -320,11 +366,11 @@ int pa_stream_disconnect(pa_stream *s); * 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.*/ + * 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 length /**< The length of the data to write */, + size_t nbytes /**< The length of the data to write in bytes*/, pa_free_cb_t free_cb /**< A cleanup routine for the data or NULL to request an internal copy */, 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 */); @@ -333,20 +379,20 @@ int pa_stream_write( * 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 */ + * buffer. If no data is available will return a NULL pointer */ int pa_stream_peek( pa_stream *p /**< The stream to use */, const void **data /**< Pointer to pointer that will point to data */, - size_t *length /**< The length of the data read */); + size_t *nbytes /**< The length of the data read in bytes */); /** Remove the current fragment on record streams. It is invalid to do this without first - * calling pa_stream_peek(). \since 0.8 */ + * calling pa_stream_peek(). */ int pa_stream_drop(pa_stream *p); -/** Return the nember of bytes that may be written using pa_stream_write() */ +/** 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 */ +/** Return the number of bytes that may be read using pa_stream_read()*/ size_t pa_stream_readable_size(pa_stream *p); /** Drain a playback stream. Use this for notification when the buffer is empty */ @@ -366,36 +412,63 @@ void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void * 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 */ + * Return the number of bytes read.*/ 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 */ +/** Set the callback function that is called when a buffer overflow happens. (Only for playback streams) */ void pa_stream_set_overflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); -/** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) \since 0.8 */ +/** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) */ void pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); -/** Set the callback function that is called whenever a latency information update happens. Useful on PA_STREAM_AUTO_TIMING_UPDATE streams only. (Only for playback streams) \since 0.8.2 */ +/** Set the callback function that is called when a the server starts + * playback after an underrun or on initial startup. This only informs + * that audio is flowing again, it is no indication that audio startet + * to reach the speakers already. (Only for playback streams). \since + * 0.9.11 */ +void pa_stream_set_started_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever a latency + * information update happens. Useful on PA_STREAM_AUTO_TIMING_UPDATE + * streams only. (Only for playback streams) */ void pa_stream_set_latency_update_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 */ +/** 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. */ 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 */ + * better off using the parameter delta of pa_stream_write() instead + * of this function. Available on both playback and recording + * streams. */ 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 */ + * structure. Available for playback streams only. */ pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); /** Request immediate start of playback on this stream. This disables - * prebuffering as specified in the pa_buffer_attr - * structure, temporarily. Available for playback streams only. \since 0.3 */ + * prebuffering as specified in the pa_buffer_attr structure, + * temporarily. Available for playback streams only. */ pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); -/** Rename the stream. \since 0.5 */ +/** Rename the stream. */ pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata); /** Return the current playback/recording time. This is based on the @@ -412,13 +485,13 @@ pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_succe * 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 */ + * able to deal with time going 'backwards'. */ 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 */ + * played. In this case *negative is set to 1. */ 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 @@ -430,13 +503,13 @@ int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative); * 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 */ + * recieved. */ const pa_timing_info* pa_stream_get_timing_info(pa_stream *s); -/** Return a pointer to the stream's sample specification. \since 0.6 */ +/** Return a pointer to the stream's sample specification. */ const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s); -/** Return a pointer to the stream's channel map. \since 0.8 */ +/** Return a pointer to the stream's channel map. */ const pa_channel_map* pa_stream_get_channel_map(pa_stream *s); /** Return the buffer metrics of the stream. Only valid after the @@ -444,6 +517,43 @@ const pa_channel_map* pa_stream_get_channel_map(pa_stream *s); * 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); + +/** Update the property list of the sink input/source output of this + * stream, adding new entries. Please note that it is highly + * recommended to set as much properties initially via + * pa_stream_new_with_proplist() as possible instead a posteriori with + * this function, since that information may then be used to route + * this stream to the right device. \since 0.9.11 */ +pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata); + +/** Update the property list of the sink input/source output of this + * stream, remove entries. \since 0.9.11 */ +pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata); + +/** For record streams connected to a monitor source: monitor only a + * very specific sink input of the sink. Thus function needs to be + * called before pa_stream_connect_record() is called. \since + * 0.9.11 */ +int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx); + +/** Return what has been set with pa_stream_set_monitor_stream() + * ebfore. \since 0.9.11 */ +uint32_t pa_stream_get_monitor_stream(pa_stream *s); + PA_C_DECL_END #endif diff --git a/src/pulse/subscribe.c b/src/pulse/subscribe.c index c1d88912..d9c06b7e 100644 --- a/src/pulse/subscribe.c +++ b/src/pulse/subscribe.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,10 +23,11 @@ #include <config.h> #endif -#include <assert.h> #include <stdio.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> + +#include <pulsecore/macro.h> #include <pulsecore/pstream-util.h> #include "internal.h" @@ -38,10 +39,11 @@ void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSE pa_subscription_event_type_t e; uint32_t idx; - assert(pd); - assert(command == PA_COMMAND_SUBSCRIBE_EVENT); - assert(t); - assert(c); + 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); @@ -65,11 +67,11 @@ pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_c pa_tagstruct *t; uint32_t tag; - assert(c); - assert(c->ref >= 1); + 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); @@ -81,9 +83,12 @@ pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_c } void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) { - assert(c); - assert(c->ref >= 1); - + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + c->subscribe_callback = cb; c->subscribe_userdata = userdata; } diff --git a/src/pulse/subscribe.h b/src/pulse/subscribe.h index adbea680..0e4be8c3 100644 --- a/src/pulse/subscribe.h +++ b/src/pulse/subscribe.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulse/thread-mainloop.c b/src/pulse/thread-mainloop.c index 7e1ccfaa..6b66696c 100644 --- a/src/pulse/thread-mainloop.c +++ b/src/pulse/thread-mainloop.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,24 +24,24 @@ #include <config.h> #endif -#include <assert.h> #include <signal.h> #include <stdio.h> -#ifdef HAVE_SYS_POLL_H -#include <sys/poll.h> +#ifdef HAVE_POLL_H +#include <poll.h> #else -#include "../pulsecore/poll.h" +#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 "mainloop.h" #include "thread-mainloop.h" struct pa_threaded_mainloop { @@ -60,7 +61,7 @@ static int poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void pa_mutex *mutex = userdata; int r; - assert(mutex); + pa_assert(mutex); /* Before entering poll() we unlock the mutex, so that * avahi_simple_poll_quit() can succeed from another thread. */ @@ -100,7 +101,7 @@ pa_threaded_mainloop *pa_threaded_mainloop_new(void) { return NULL; } - m->mutex = pa_mutex_new(1); + m->mutex = pa_mutex_new(TRUE, TRUE); m->cond = pa_cond_new(); m->accept_cond = pa_cond_new(); m->thread = NULL; @@ -113,10 +114,10 @@ pa_threaded_mainloop *pa_threaded_mainloop_new(void) { } void pa_threaded_mainloop_free(pa_threaded_mainloop* m) { - assert(m); + pa_assert(m); /* Make sure that this function is not called from the helper thread */ - assert((m->thread && !pa_thread_is_running(m->thread)) || !in_worker(m)); + pa_assert((m->thread && !pa_thread_is_running(m->thread)) || !in_worker(m)); pa_threaded_mainloop_stop(m); @@ -128,14 +129,14 @@ void pa_threaded_mainloop_free(pa_threaded_mainloop* m) { 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) { - assert(m); + pa_assert(m); - assert(!m->thread || !pa_thread_is_running(m->thread)); + pa_assert(!m->thread || !pa_thread_is_running(m->thread)); if (!(m->thread = pa_thread_new(thread, m))) return -1; @@ -144,13 +145,13 @@ int pa_threaded_mainloop_start(pa_threaded_mainloop *m) { } void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) { - assert(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 */ - assert(!in_worker(m)); + pa_assert(!in_worker(m)); pa_mutex_lock(m->mutex); pa_mainloop_quit(m->real_mainloop, 0); @@ -160,25 +161,25 @@ void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) { } void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) { - assert(m); - + pa_assert(m); + /* Make sure that this function is not called from the helper thread */ - assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + 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) { - assert(m); - + pa_assert(m); + /* Make sure that this function is not called from the helper thread */ - assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + 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) { - assert(m); + pa_assert(m); pa_cond_signal(m->cond, 1); @@ -187,36 +188,42 @@ void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept) { } void pa_threaded_mainloop_wait(pa_threaded_mainloop *m) { - assert(m); - + pa_assert(m); + /* Make sure that this function is not called from the helper thread */ - assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); m->n_waiting ++; pa_cond_wait(m->cond, m->mutex); - assert(m->n_waiting > 0); + pa_assert(m->n_waiting > 0); m->n_waiting --; } void pa_threaded_mainloop_accept(pa_threaded_mainloop *m) { - assert(m); - + pa_assert(m); + /* Make sure that this function is not called from the helper thread */ - assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + 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) { - assert(m); + pa_assert(m); return pa_mainloop_get_retval(m->real_mainloop); } pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m) { - assert(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 index 44eff5a3..521e29b0 100644 --- a/src/pulse/thread-mainloop.h +++ b/src/pulse/thread-mainloop.h @@ -1,21 +1,22 @@ #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 @@ -149,7 +150,7 @@ PA_C_DECL_BEGIN * 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. + * 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. * @@ -229,7 +230,7 @@ PA_C_DECL_BEGIN */ /** \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 @@ -294,6 +295,9 @@ 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 index 11285230..9708a735 100644 --- a/src/pulse/timeval.c +++ b/src/pulse/timeval.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <stddef.h> #include <sys/time.h> @@ -31,15 +31,17 @@ #include <windows.h> #endif -#include "../pulsecore/winsock.h" +#include <pulsecore/winsock.h> +#include <pulsecore/macro.h> #include "timeval.h" struct timeval *pa_gettimeofday(struct timeval *tv) { #ifdef HAVE_GETTIMEOFDAY - assert(tv); - - return gettimeofday(tv, NULL) < 0 ? NULL : tv; + pa_assert(tv); + + pa_assert_se(gettimeofday(tv, NULL) == 0); + return tv; #elif defined(OS_IS_WIN32) /* * Copied from implementation by Steven Edwards (LGPL). @@ -56,7 +58,7 @@ struct timeval *pa_gettimeofday(struct timeval *tv) { LARGE_INTEGER li; __int64 t; - assert(tv); + pa_assert(tv); GetSystemTimeAsFileTime(&ft); li.LowPart = ft.dwLowDateTime; @@ -64,8 +66,8 @@ struct timeval *pa_gettimeofday(struct timeval *tv) { t = li.QuadPart; /* In 100-nanosecond intervals */ t -= EPOCHFILETIME; /* Offset to the Epoch time */ t /= 10; /* In microseconds */ - tv->tv_sec = (long)(t / 1000000); - tv->tv_usec = (long)(t % 1000000); + tv->tv_sec = (time_t) (t / PA_USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (t % PA_USEC_PER_SEC); return tv; #else @@ -75,9 +77,11 @@ struct timeval *pa_gettimeofday(struct timeval *tv) { pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { pa_usec_t r; - assert(a && b); - /* Check which whan is the earlier time and swap the two arguments if reuqired. */ + 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; @@ -86,7 +90,7 @@ pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { } /* Calculate the second difference*/ - r = ((pa_usec_t) a->tv_sec - b->tv_sec)* 1000000; + 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) @@ -98,7 +102,8 @@ pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { } int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) { - assert(a && b); + pa_assert(a); + pa_assert(b); if (a->tv_sec < b->tv_sec) return -1; @@ -117,26 +122,61 @@ int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) { pa_usec_t pa_timeval_age(const struct timeval *tv) { struct timeval now; - assert(tv); - + 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; - assert(tv); - - secs = (v/1000000); - tv->tv_sec += (unsigned long) secs; - v -= secs*1000000; + 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 += v; + tv->tv_usec += (suseconds_t) v; /* Normalize */ - while (tv->tv_usec >= 1000000) { + while ((unsigned) tv->tv_usec >= PA_USEC_PER_SEC) { tv->tv_sec++; - tv->tv_usec -= 1000000; + tv->tv_usec -= PA_USEC_PER_SEC; + } + + return tv; +} + +struct timeval* pa_timeval_sub(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; + + if (tv->tv_usec >= (suseconds_t) v) + tv->tv_usec -= (suseconds_t) v; + else { + tv->tv_sec --; + tv->tv_usec = tv->tv_usec + PA_USEC_PER_SEC - v; } 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 index e2dbbadb..ee398296 100644 --- a/src/pulse/timeval.h +++ b/src/pulse/timeval.h @@ -1,21 +1,22 @@ #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 @@ -23,6 +24,7 @@ ***/ #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> #include <pulse/sample.h> /** \file @@ -30,6 +32,13 @@ PA_C_DECL_BEGIN +#define PA_MSEC_PER_SEC ((pa_usec_t) 1000ULL) +#define PA_USEC_PER_SEC ((pa_usec_t) 1000000ULL) +#define PA_NSEC_PER_SEC ((pa_usec_t) 1000000000ULL) +#define PA_USEC_PER_MSEC ((pa_usec_t) 1000ULL) +#define PA_NSEC_PER_MSEC ((pa_usec_t) 1000000ULL) +#define PA_NSEC_PER_USEC ((pa_usec_t) 1000ULL) + struct timeval; /** Return the current timestamp, just like UNIX gettimeofday() */ @@ -37,10 +46,10 @@ 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_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); +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); @@ -48,6 +57,15 @@ 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); +/** Subtract the specified time inmicroseconds to the specified timeval structure. \since 0.9.11 */ +struct timeval* pa_timeval_sub(struct timeval *tv, pa_usec_t v); + +/** 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 index 33fa7214..119be542 100644 --- a/src/pulse/utf8.c +++ b/src/pulse/utf8.c @@ -1,4 +1,24 @@ -/* $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. */ @@ -15,7 +35,7 @@ * * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public @@ -28,7 +48,6 @@ #include <config.h> #endif -#include <assert.h> #include <errno.h> #include <stdlib.h> #include <inttypes.h> @@ -38,12 +57,15 @@ #include <iconv.h> #endif +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + #include "utf8.h" -#include "xmalloc.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 */ @@ -52,6 +74,7 @@ static inline int is_unicode_valid(uint32_t ch) { return 0; if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ return 0; + return 1; } @@ -73,6 +96,8 @@ static char* utf8_validate(const char *str, char *output) { int size; uint8_t *o; + pa_assert(str); + o = (uint8_t*) output; for (p = (const uint8_t*) str; *p; p++) { if (*p < 128) { @@ -130,7 +155,7 @@ ONE_REMAINING: if (o) o++; - + continue; error: @@ -156,15 +181,15 @@ failure: return NULL; } -const char* pa_utf8_valid (const char *str) { +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); } @@ -173,22 +198,24 @@ char* pa_utf8_filter (const char *str) { static char* iconv_simple(const char *str, const char *to, const char *from) { char *new_str; size_t len, inlen; - iconv_t cd; ICONV_CONST char *inbuf; char *outbuf; size_t res, inbytes, outbytes; + pa_assert(str); + pa_assert(to); + pa_assert(from); + cd = iconv_open(to, from); if (cd == (iconv_t)-1) return NULL; inlen = len = strlen(str) + 1; - new_str = pa_xmalloc(len); - assert(new_str); + new_str = pa_xnew(char, len); - while (1) { - inbuf = (ICONV_CONST char*)str; /* Brain dead prototype for iconv() */ + for (;;) { + inbuf = (ICONV_CONST char*) str; /* Brain dead prototype for iconv() */ inbytes = inlen; outbuf = new_str; outbytes = len; @@ -204,11 +231,10 @@ static char* iconv_simple(const char *str, const char *to, const char *from) { break; } - assert(inbytes != 0); + pa_assert(inbytes != 0); len += inbytes; new_str = pa_xrealloc(new_str, len); - assert(new_str); } iconv_close(cd); @@ -227,10 +253,12 @@ char* pa_locale_to_utf8 (const char *str) { #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; } diff --git a/src/pulse/utf8.h b/src/pulse/utf8.h index 2eac724d..6c7e7a5b 100644 --- a/src/pulse/utf8.h +++ b/src/pulse/utf8.h @@ -1,21 +1,22 @@ #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 @@ -23,6 +24,7 @@ ***/ #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> /** \file * UTF8 Validation functions @@ -31,7 +33,7 @@ PA_C_DECL_BEGIN /** Test if the specified strings qualifies as valid UTF8. Return the string if so, otherwise NULL */ -const char *pa_utf8_valid(const char *str); +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); diff --git a/src/pulse/util.c b/src/pulse/util.c index c13951da..c0911b51 100644 --- a/src/pulse/util.c +++ b/src/pulse/util.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <errno.h> #include <limits.h> #include <stdio.h> @@ -53,33 +53,32 @@ #include <sys/prctl.h> #endif -#include "../pulsecore/winsock.h" - +#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" -#ifndef OS_IS_WIN32 -#define PATH_SEP '/' -#else -#define PATH_SEP '\\' -#endif - char *pa_get_user_name(char *s, size_t l) { - char *p; + const char *p; char buf[1024]; #ifdef HAVE_PWD_H struct passwd pw, *r; #endif - assert(s && l > 0); + pa_assert(s); + pa_assert(l > 0); - if (!(p = getenv("USER")) && !(p = getenv("LOGNAME")) && !(p = getenv("USERNAME"))) { + if (!(p = (getuid() == 0 ? "root" : NULL)) && + !(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 @@ -87,10 +86,10 @@ char *pa_get_user_name(char *s, size_t l) { * that do not support getpwuid_r. */ if ((r = getpwuid(getuid())) == NULL) { #endif - snprintf(s, l, "%lu", (unsigned long) getuid()); + pa_snprintf(s, l, "%lu", (unsigned long) getuid()); return s; } - + p = r->pw_name; #elif defined(OS_IS_WIN32) /* HAVE_PWD_H */ @@ -110,11 +109,15 @@ char *pa_get_user_name(char *s, size_t l) { } char *pa_get_host_name(char *s, size_t l) { - assert(s && l > 0); + + 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; } @@ -127,7 +130,8 @@ char *pa_get_home_dir(char *s, size_t l) { struct passwd pw, *r; #endif - assert(s && l); + pa_assert(s); + pa_assert(l > 0); if ((e = getenv("HOME"))) return pa_strlcpy(s, e, l); @@ -156,32 +160,32 @@ char *pa_get_home_dir(char *s, size_t l) { char *pa_get_binary_name(char *s, size_t l) { - assert(s); - assert(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 HAVE_READLINK + +#ifdef __linux__ { - int i; - char path[PATH_MAX]; + char *rp; /* This works on Linux only */ - - if ((i = readlink("/proc/self/exe", path, sizeof(path)-1)) >= 0) { - path[i] = 0; - return pa_strlcpy(s, pa_path_get_filename(path), l); + + 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) { @@ -192,32 +196,37 @@ char *pa_get_binary_name(char *s, size_t l) { 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; } -const char *pa_path_get_filename(const char *p) { +char *pa_path_get_filename(const char *p) { char *fn; - if ((fn = strrchr(p, PATH_SEP))) + pa_assert(p); + + if ((fn = strrchr(p, PA_PATH_SEP_CHAR))) return fn+1; - return (const char*) p; + return (char*) p; } char *pa_get_fqdn(char *s, size_t l) { char hn[256]; -#ifdef HAVE_GETADDRINFO +#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; @@ -225,7 +234,7 @@ char *pa_get_fqdn(char *s, size_t l) { 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); diff --git a/src/pulse/util.h b/src/pulse/util.h index 5c03b0a9..cf06d4fd 100644 --- a/src/pulse/util.h +++ b/src/pulse/util.h @@ -1,21 +1,22 @@ #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 @@ -25,6 +26,7 @@ #include <stddef.h> #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> /** \file * Assorted utility functions */ @@ -49,7 +51,7 @@ char *pa_get_binary_name(char *s, size_t l); /** Return a pointer to the filename inside a path (which is the last * component). */ -const char *pa_path_get_filename(const char *p); +char *pa_path_get_filename(const char *p); /** Wait t milliseconds */ int pa_msleep(unsigned long t); diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in index 748541a1..e6226c44 100644 --- a/src/pulse/version.h.in +++ b/src/pulse/version.h.in @@ -1,21 +1,22 @@ #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 @@ -36,7 +37,8 @@ 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. */ +/** 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 @@ -44,8 +46,8 @@ const char* pa_get_library_version(void); * 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 */ +/** The current protocol version. Version 8 relates to Polypaudio + * 0.8/PulseAudio 0.9. */ #define PA_PROTOCOL_VERSION @PA_PROTOCOL_VERSION@ PA_C_DECL_END diff --git a/src/pulse/volume.c b/src/pulse/volume.c index 530814e0..70d6f86a 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,20 +23,22 @@ #include <config.h> #endif -#include <assert.h> #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; - assert(a); - assert(b); + 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; @@ -46,10 +48,10 @@ int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) { pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) { int i; - - assert(a); - assert(channels > 0); - assert(channels <= PA_CHANNELS_MAX); + + pa_assert(a); + pa_assert(channels > 0); + pa_assert(channels <= PA_CHANNELS_MAX); a->channels = channels; @@ -62,7 +64,7 @@ pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) { pa_volume_t pa_cvolume_avg(const pa_cvolume *a) { uint64_t sum = 0; int i; - assert(a); + pa_assert(a); for (i = 0; i < a->channels; i++) sum += a->values[i]; @@ -76,10 +78,10 @@ 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 +#define USER_DECIBEL_RANGE 60 pa_volume_t pa_sw_volume_from_dB(double dB) { - if (dB <= -USER_DECIBEL_RANGE) + if (isinf(dB) < 0 || dB <= -USER_DECIBEL_RANGE) return PA_VOLUME_MUTED; return (pa_volume_t) ((dB/USER_DECIBEL_RANGE+1)*PA_VOLUME_NORM); @@ -115,15 +117,15 @@ char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { unsigned channel; int first = 1; char *e; - - assert(s); - assert(l > 0); - assert(c); + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); *(e = s) = 0; for (channel = 0; channel < c->channels && l > 1; channel++) { - l -= snprintf(e, l, "%s%u: %3u%%", + l -= pa_snprintf(e, l, "%s%u: %3u%%", first ? "" : " ", channel, (c->values[channel]*100)/PA_VOLUME_NORM); @@ -138,7 +140,7 @@ char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { /** 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; - assert(a); + pa_assert(a); for (c = 0; c < a->channels; c++) if (a->values[c] != v) @@ -149,10 +151,10 @@ int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) { pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { unsigned i; - - assert(dest); - assert(a); - assert(b); + + pa_assert(dest); + pa_assert(a); + pa_assert(b); for (i = 0; i < a->channels && i < b->channels && i < PA_CHANNELS_MAX; i++) { @@ -167,7 +169,7 @@ pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const } int pa_cvolume_valid(const pa_cvolume *v) { - assert(v); + pa_assert(v); if (v->channels <= 0 || v->channels > PA_CHANNELS_MAX) return 0; diff --git a/src/pulse/volume.h b/src/pulse/volume.h index 6c60223a..3befb1da 100644 --- a/src/pulse/volume.h +++ b/src/pulse/volume.h @@ -1,21 +1,22 @@ #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 @@ -23,7 +24,9 @@ ***/ #include <inttypes.h> + #include <pulse/cdecl.h> +#include <pulse/gccmacro.h> #include <pulse/sample.h> /** \page volume Volume Control @@ -98,10 +101,10 @@ PA_C_DECL_BEGIN typedef uint32_t pa_volume_t; /** Normal volume (100%) */ -#define PA_VOLUME_NORM (0x10000) +#define PA_VOLUME_NORM ((pa_volume_t) 0x10000U) /** Muted volume (0%) */ -#define PA_VOLUME_MUTED (0) +#define PA_VOLUME_MUTED ((pa_volume_t) 0U) /** A structure encapsulating a per-channel volume */ typedef struct pa_cvolume { @@ -110,7 +113,7 @@ typedef struct pa_cvolume { } pa_cvolume; /** Return non-zero when *a == *b */ -int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *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) @@ -128,13 +131,13 @@ pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v); 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_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); +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); +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) @@ -143,28 +146,28 @@ int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v); #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_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); -/** 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); +/** Convert a decibel value to a volume. This is only valid for software volumes! */ +pa_volume_t pa_sw_volume_from_dB(double f) PA_GCC_CONST; -/** Convert a volume to a decibel value. This is only valid for software volumes! \since 0.4 */ -double pa_sw_volume_to_dB(pa_volume_t v); +/** Convert a volume to a decibel value. This is only valid for software volumes! */ +double pa_sw_volume_to_dB(pa_volume_t v) PA_GCC_CONST; -/** Convert a linear factor to a volume. This is only valid for software volumes! \since 0.8 */ -pa_volume_t pa_sw_volume_from_linear(double v); +/** Convert a linear factor to a volume. This is only valid for software volumes! */ +pa_volume_t pa_sw_volume_from_linear(double v) PA_GCC_CONST; -/** Convert a volume to a linear factor. This is only valid for software volumes! \since 0.8 */ -double pa_sw_volume_to_linear(pa_volume_t v); +/** Convert a volume to a linear factor. This is only valid for software volumes! */ +double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST; #ifdef INFINITY -#define PA_DECIBEL_MININFTY (-INFINITY) +#define PA_DECIBEL_MININFTY ((double) -INFINITY) #else -/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). \since 0.4 */ -#define PA_DECIBEL_MININFTY (-200) +/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). */ +#define PA_DECIBEL_MININFTY ((double) -200.0) #endif PA_C_DECL_END diff --git a/src/pulse/xmalloc.c b/src/pulse/xmalloc.c index 36755166..90237013 100644 --- a/src/pulse/xmalloc.c +++ b/src/pulse/xmalloc.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -25,12 +25,13 @@ #include <stdlib.h> #include <signal.h> -#include <assert.h> #include <unistd.h> #include <string.h> +#include <errno.h> +#include <pulse/gccmacro.h> #include <pulsecore/core-util.h> -#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> #include "xmalloc.h" @@ -58,31 +59,31 @@ static void oom(void) { void* pa_xmalloc(size_t size) { void *p; - assert(size > 0); - assert(size < MAX_ALLOC_SIZE); - + 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; - assert(size > 0); - assert(size < MAX_ALLOC_SIZE); - + 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; - assert(size > 0); - assert(size < MAX_ALLOC_SIZE); - + pa_assert(size > 0); + pa_assert(size < MAX_ALLOC_SIZE); + if (!(p = realloc(ptr, size))) oom(); return p; @@ -107,7 +108,7 @@ char *pa_xstrdup(const char *s) { char *pa_xstrndup(const char *s, size_t l) { char *e, *r; - + if (!s) return NULL; @@ -121,8 +122,12 @@ char *pa_xstrndup(const char *s, size_t l) { } void pa_xfree(void *p) { + int saved_errno; + if (!p) return; + saved_errno = errno; free(p); + errno = saved_errno; } diff --git a/src/pulse/xmalloc.h b/src/pulse/xmalloc.h index 126c495c..c453138b 100644 --- a/src/pulse/xmalloc.h +++ b/src/pulse/xmalloc.h @@ -1,21 +1,21 @@ #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 @@ -73,6 +73,15 @@ static inline void* pa_xnew0_internal(unsigned n, size_t 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/anotify.c b/src/pulsecore/anotify.c deleted file mode 100644 index a61f8442..00000000 --- a/src/pulsecore/anotify.c +++ /dev/null @@ -1,143 +0,0 @@ -/* $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 <fcntl.h> - -#include <pulse/xmalloc.h> - -#include "anotify.h" - -#define EVENTS_MAX 16 - -struct pa_anotify { - pa_mainloop_api *api; - pa_anotify_cb_t callback; - void *userdata; - int fds[2]; - pa_io_event *io_event; - pa_defer_event *defer_event; - - uint8_t queued_events[EVENTS_MAX]; - unsigned n_queued_events, queue_index; -}; - -static void dispatch_event(pa_anotify *a) { - assert(a); - assert(a->queue_index < a->n_queued_events); - - a->callback(a->queued_events[a->queue_index++], a->userdata); - - if (a->queue_index >= a->n_queued_events) { - a->n_queued_events = 0; - a->queue_index = 0; - - a->api->io_enable(a->io_event, PA_IO_EVENT_INPUT); - a->api->defer_enable(a->defer_event, 0); - } else { - a->api->io_enable(a->io_event, 0); - a->api->defer_enable(a->defer_event, 1); - } -} - -static void io_callback( - pa_mainloop_api *api, - pa_io_event *e, - int fd, - pa_io_event_flags_t events, - void *userdata) { - - pa_anotify *a = userdata; - ssize_t r; - - assert(a); - assert(events == PA_IO_EVENT_INPUT); - assert(a->n_queued_events == 0); - - r = read(fd, a->queued_events, sizeof(a->queued_events)); - assert(r > 0); - - a->n_queued_events = (unsigned) r; - a->queue_index = 0; - - /* Only dispatch a single event */ - dispatch_event(a); -} - -static void defer_callback(pa_mainloop_api *api, pa_defer_event *e, void *userdata) { - pa_anotify *a = userdata; - assert(a); - - dispatch_event(a); -} - -pa_anotify *pa_anotify_new(pa_mainloop_api*api, pa_anotify_cb_t cb, void *userdata) { - pa_anotify *a; - - assert(api); - assert(cb); - - a = pa_xnew(pa_anotify, 1); - - if (pipe(a->fds) < 0) { - pa_xfree(a); - return NULL; - } - - a->api = api; - a->callback = cb; - a->userdata = userdata; - - a->io_event = api->io_new(api, a->fds[0], PA_IO_EVENT_INPUT, io_callback, a); - a->defer_event = api->defer_new(api, defer_callback, a); - a->api->defer_enable(a->defer_event, 0); - - a->n_queued_events = 0; - - return a; -} - -void pa_anotify_free(pa_anotify *a) { - assert(a); - - a->api->io_free(a->io_event); - a->api->defer_free(a->defer_event); - - if (a->fds[0] >= 0) - close(a->fds[0]); - if (a->fds[1] >= 0) - close(a->fds[1]); - - pa_xfree(a); -} - -int pa_anotify_signal(pa_anotify *a, uint8_t event) { - ssize_t r; - assert(a); - - r = write(a->fds[1], &event, 1); - return r != 1 ? -1 : 0; -} diff --git a/src/pulsecore/asyncmsgq.c b/src/pulsecore/asyncmsgq.c new file mode 100644 index 00000000..5c7af2a8 --- /dev/null +++ b/src/pulsecore/asyncmsgq.c @@ -0,0 +1,319 @@ +/*** + 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_asyncq_post(a->asyncq, i); + pa_mutex_unlock(a->mutex); +} + +int pa_asyncmsgq_send(pa_asyncmsgq *a, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *chunk) { + struct asyncmsgq_item i; + pa_assert(PA_REFCNT_VALUE(a) > 0); + + i.code = code; + i.object = object; + i.userdata = (void*) userdata; + i.free_cb = NULL; + i.ret = -1; + i.offset = offset; + if (chunk) { + pa_assert(chunk->memblock); + i.memchunk = *chunk; + } else + pa_memchunk_reset(&i.memchunk); + + if (!(i.semaphore = pa_flist_pop(PA_STATIC_FLIST_GET(semaphores)))) + i.semaphore = pa_semaphore_new(0); + + 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, TRUE) == 0); + pa_mutex_unlock(a->mutex); + + pa_semaphore_wait(i.semaphore); + + if (pa_flist_push(PA_STATIC_FLIST_GET(semaphores), i.semaphore) < 0) + pa_semaphore_free(i.semaphore); + + return i.ret; +} + +int pa_asyncmsgq_get(pa_asyncmsgq *a, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *chunk, pa_bool_t 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_read_fd(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_read_fd(a->asyncq); +} + +int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_read_before_poll(a->asyncq); +} + +void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncq_read_after_poll(a->asyncq); +} + +int pa_asyncmsgq_write_fd(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_write_fd(a->asyncq); +} + +void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncq_write_before_poll(a->asyncq); +} + +void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncq_write_after_poll(a->asyncq); +} + +int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk) { + + if (object) + return object->process_msg(object, code, userdata, offset, memchunk); + + return 0; +} diff --git a/src/pulsecore/asyncmsgq.h b/src/pulsecore/asyncmsgq.h new file mode 100644 index 00000000..1f38207a --- /dev/null +++ b/src/pulsecore/asyncmsgq.h @@ -0,0 +1,79 @@ +#ifndef foopulseasyncmsgqhfoo +#define foopulseasyncmsgqhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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, pa_bool_t 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); + +/* For the reading side */ +int pa_asyncmsgq_read_fd(pa_asyncmsgq *q); +int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a); +void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a); + +/* For the write side */ +int pa_asyncmsgq_write_fd(pa_asyncmsgq *q); +void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a); +void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a); + +#endif diff --git a/src/pulsecore/asyncq.c b/src/pulsecore/asyncq.c new file mode 100644 index 00000000..03e9f0df --- /dev/null +++ b/src/pulsecore/asyncq.c @@ -0,0 +1,321 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to 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 <pulsecore/llist.h> +#include <pulsecore/flist.h> +#include <pulse/xmalloc.h> + +#include "asyncq.h" +#include "fdsem.h" + +#define ASYNCQ_SIZE 256 + +/* For debugging purposes we can define _Y to put an extra thread + * yield between each operation. */ + +/* #define PROFILE */ + +#ifdef PROFILE +#define _Y pa_thread_yield() +#else +#define _Y do { } while(0) +#endif + +struct localq { + void *data; + PA_LLIST_FIELDS(struct localq); +}; + +struct pa_asyncq { + unsigned size; + unsigned read_idx; + unsigned write_idx; + pa_fdsem *read_fdsem, *write_fdsem; + + PA_LLIST_HEAD(struct localq, localq); + struct localq *last_localq; + pa_bool_t waiting_for_post; +}; + +PA_STATIC_FLIST_DECLARE(localq, 0, pa_xfree); + +#define PA_ASYNCQ_CELLS(x) ((pa_atomic_ptr_t*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_asyncq)))) + +static 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(pa_is_power_of_two(size)); + + l = pa_xmalloc0(PA_ALIGN(sizeof(pa_asyncq)) + (sizeof(pa_atomic_ptr_t) * size)); + + l->size = size; + + PA_LLIST_HEAD_INIT(struct localq, l->localq); + l->last_localq = NULL; + l->waiting_for_post = FALSE; + + if (!(l->read_fdsem = pa_fdsem_new())) { + pa_xfree(l); + return NULL; + } + + if (!(l->write_fdsem = pa_fdsem_new())) { + pa_fdsem_free(l->read_fdsem); + pa_xfree(l); + return NULL; + } + + return l; +} + +void pa_asyncq_free(pa_asyncq *l, pa_free_cb_t free_cb) { + struct localq *q; + pa_assert(l); + + if (free_cb) { + void *p; + + while ((p = pa_asyncq_pop(l, 0))) + free_cb(p); + } + + while ((q = l->localq)) { + if (free_cb) + free_cb(q->data); + + PA_LLIST_REMOVE(struct localq, l->localq, q); + + if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0) + pa_xfree(q); + } + + pa_fdsem_free(l->read_fdsem); + pa_fdsem_free(l->write_fdsem); + pa_xfree(l); +} + +static int push(pa_asyncq*l, void *p, pa_bool_t 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; +} + +static pa_bool_t flush_postq(pa_asyncq *l) { + struct localq *q; + + pa_assert(l); + + while ((q = l->last_localq)) { + + if (push(l, q->data, FALSE) < 0) + return FALSE; + + l->last_localq = q->prev; + + PA_LLIST_REMOVE(struct localq, l->localq, q); + + if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0) + pa_xfree(q); + } + + return TRUE; +} + +int pa_asyncq_push(pa_asyncq*l, void *p, pa_bool_t wait) { + pa_assert(l); + + if (!flush_postq(l)) + return -1; + + return push(l, p, wait); +} + +void pa_asyncq_post(pa_asyncq*l, void *p) { + struct localq *q; + + pa_assert(l); + pa_assert(p); + + if (pa_asyncq_push(l, p, FALSE) >= 0) + return; + + /* OK, we couldn't push anything in the queue. So let's queue it + * locally and push it later */ + + pa_log("q overrun, queuing locally"); + + if (!(q = pa_flist_pop(PA_STATIC_FLIST_GET(localq)))) + q = pa_xnew(struct localq, 1); + + q->data = p; + PA_LLIST_PREPEND(struct localq, l->localq, q); + + if (!l->last_localq) + l->last_localq = q; + + return; +} + +void* pa_asyncq_pop(pa_asyncq*l, pa_bool_t 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_read_fd(pa_asyncq *q) { + pa_assert(q); + + return pa_fdsem_get(q->write_fdsem); +} + +int pa_asyncq_read_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_read_after_poll(pa_asyncq *l) { + pa_assert(l); + + pa_fdsem_after_poll(l->write_fdsem); +} + +int pa_asyncq_write_fd(pa_asyncq *q) { + pa_assert(q); + + return pa_fdsem_get(q->read_fdsem); +} + +void pa_asyncq_write_before_poll(pa_asyncq *l) { + pa_assert(l); + + for (;;) { + + if (flush_postq(l)) + break; + + if (pa_fdsem_before_poll(l->read_fdsem) >= 0) { + l->waiting_for_post = TRUE; + break; + } + } +} + +void pa_asyncq_write_after_poll(pa_asyncq *l) { + pa_assert(l); + + if (l->waiting_for_post) { + pa_fdsem_after_poll(l->read_fdsem); + l->waiting_for_post = FALSE; + } +} diff --git a/src/pulsecore/asyncq.h b/src/pulsecore/asyncq.h new file mode 100644 index 00000000..e6847ab8 --- /dev/null +++ b/src/pulsecore/asyncq.h @@ -0,0 +1,65 @@ +#ifndef foopulseasyncqhfoo +#define foopulseasyncqhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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> +#include <pulsecore/macro.h> + +/* A simple, asynchronous, lock-free (if requested also wait-free) + * queue. Not multiple-reader/multiple-writer safe. If that is + * required both sides can be protected by a mutex each. --- Which is + * not a bad thing in most cases, since this queue is intended for + * communication between a normal thread and a single real-time + * thread. Only the real-time side needs to be lock-free/wait-free. + * + * If the queue is full and another entry shall be pushed, or when the + * queue is empty and another entry shall be popped and the "wait" + * argument is non-zero, the queue will block on a UNIX FIFO object -- + * that will probably require locking on the kernel side -- which + * however is probably not problematic, because we do it only on + * starvation or overload in which case we have to block anyway. */ + +typedef struct pa_asyncq pa_asyncq; + +pa_asyncq* pa_asyncq_new(unsigned size); +void pa_asyncq_free(pa_asyncq* q, pa_free_cb_t free_cb); + +void* pa_asyncq_pop(pa_asyncq *q, pa_bool_t wait); +int pa_asyncq_push(pa_asyncq *q, void *p, pa_bool_t wait); + +/* Similar to pa_asyncq_push(), but if the queue is full, postpone it + * locally and delay until pa_asyncq_before_poll_post() */ +void pa_asyncq_post(pa_asyncq*l, void *p); + +/* For the reading side */ +int pa_asyncq_read_fd(pa_asyncq *q); +int pa_asyncq_read_before_poll(pa_asyncq *a); +void pa_asyncq_read_after_poll(pa_asyncq *a); + +/* For the writing side */ +int pa_asyncq_write_fd(pa_asyncq *q); +void pa_asyncq_write_before_poll(pa_asyncq *a); +void pa_asyncq_write_after_poll(pa_asyncq *a); + +#endif diff --git a/src/pulsecore/atomic.h b/src/pulsecore/atomic.h index 8d608b5b..a91c4d56 100644 --- a/src/pulsecore/atomic.h +++ b/src/pulsecore/atomic.h @@ -1,65 +1,452 @@ #ifndef foopulseatomichfoo #define foopulseatomichfoo -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2006-2008 Lennart Poettering + Copyright 2008 Nokia Corporation + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General 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 <atomic_ops.h> - -/* atomic_ops guarantees us that sizeof(AO_t) == sizeof(void*). +/* + * 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. * - * It is not guaranteed however, that sizeof(AO_t) == sizeof(size_t). - * however very likely. */ + * 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 -typedef struct pa_atomic_int { - volatile AO_t value; -} pa_atomic_int_t; +#ifdef HAVE_ATOMIC_BUILTINS + +/* __sync based implementation */ + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; #define PA_ATOMIC_INIT(v) { .value = (v) } -/* 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 */ +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; +} + +#elif defined(ATOMIC_ARM_INLINE_ASM) + +/* + These should only be enabled if we have ARMv6 or better. +*/ + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline void pa_memory_barrier(void) { +#ifdef ATOMIC_ARM_MEMORY_BARRIER_ENABLED + asm volatile ("mcr p15, 0, r0, c7, c10, 5 @ dmb"); +#endif +} + +static inline int pa_atomic_load(const pa_atomic_t *a) { + pa_memory_barrier(); + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; + pa_memory_barrier(); +} + +/* Returns the previously set value */ +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + unsigned long not_exclusive; + int new_val, old_val; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%3]\n" + "add %2, %0, %4\n" + "strex %1, %2, [%3]\n" + : "=&r" (old_val), "=&r" (not_exclusive), "=&r" (new_val) + : "r" (&a->value), "Ir" (i) + : "cc"); + } while(not_exclusive); + pa_memory_barrier(); + + return old_val; +} + +/* Returns the previously set value */ +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + unsigned long not_exclusive; + int new_val, old_val; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%3]\n" + "sub %2, %0, %4\n" + "strex %1, %2, [%3]\n" + : "=&r" (old_val), "=&r" (not_exclusive), "=&r" (new_val) + : "r" (&a->value), "Ir" (i) + : "cc"); + } while(not_exclusive); + pa_memory_barrier(); + + return old_val; +} + +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + unsigned long not_equal, not_exclusive; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%2]\n" + "subs %0, %0, %3\n" + "mov %1, %0\n" + "strexeq %0, %4, [%2]\n" + : "=&r" (not_exclusive), "=&r" (not_equal) + : "r" (&a->value), "Ir" (old_i), "r" (new_i) + : "cc"); + } while(not_exclusive && !not_equal); + pa_memory_barrier(); + + return !not_equal; +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + pa_memory_barrier(); + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; + pa_memory_barrier(); +} + +static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + unsigned long not_equal, not_exclusive; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%2]\n" + "subs %0, %0, %3\n" + "mov %1, %0\n" + "strexeq %0, %4, [%2]\n" + : "=&r" (not_exclusive), "=&r" (not_equal) + : "r" (&a->value), "Ir" (old_p), "r" (new_p) + : "cc"); + } while(not_exclusive && !not_equal); + pa_memory_barrier(); + + return !not_equal; +} + +#elif defined(ATOMIC_ARM_LINUX_HELPERS) + +/* See file arch/arm/kernel/entry-armv.S in your kernel sources for more + information about these functions. The arm kernel helper functions first + appeared in 2.6.16. + Apply --disable-atomic-arm-linux-helpers flag to confugure if you prefere + inline asm implementation or you have an obsolete Linux kernel. +*/ +/* Memory barrier */ +typedef void (__kernel_dmb_t)(void); +#define __kernel_dmb (*(__kernel_dmb_t *)0xffff0fa0) + +static inline void pa_memory_barrier(void) { +#ifndef ATOMIC_ARM_MEMORY_BARRIER_ENABLED + __kernel_dmb(); +#endif +} + +/* Atomic exchange (__kernel_cmpxchg_t contains memory barriers if needed) */ +typedef int (__kernel_cmpxchg_t)(int oldval, int newval, volatile int *ptr); +#define __kernel_cmpxchg (*(__kernel_cmpxchg_t *)0xffff0fc0) + +/* This is just to get rid of all warnings */ +typedef int (__kernel_cmpxchg_u_t)(unsigned long oldval, unsigned long newval, volatile unsigned long *ptr); +#define __kernel_cmpxchg_u (*(__kernel_cmpxchg_u_t *)0xffff0fc0) -static inline int pa_atomic_load(const pa_atomic_int_t *a) { +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + pa_memory_barrier(); + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; + pa_memory_barrier(); +} + +/* Returns the previously set value */ +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + int old_val; + do { + old_val = a->value; + } while(__kernel_cmpxchg(old_val, old_val + i, &a->value)); + return old_val; +} + +/* Returns the previously set value */ +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + int old_val; + do { + old_val = a->value; + } while(__kernel_cmpxchg(old_val, old_val - i, &a->value)); + return old_val; +} + +/* Returns the previously set value */ +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +/* Returns the previously set value */ +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +/* Returns non-zero when the operation was successful. */ +static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + pa_bool_t failed; + do { + failed = !!__kernel_cmpxchg(old_i, new_i, &a->value); + } while(failed && a->value == old_i); + return !failed; +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (unsigned long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + pa_memory_barrier(); + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; + pa_memory_barrier(); +} + +static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + pa_bool_t failed; + do { + failed = !!__kernel_cmpxchg_u((unsigned long) old_p, (unsigned long) new_p, &a->value); + } while(failed && a->value == (unsigned long) old_p); + return !failed; +} + +#else + +/* libatomic_ops based implementation */ + +#include <atomic_ops.h> + +typedef struct pa_atomic { + volatile AO_t value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (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_int_t *a, int i) { +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_int_t *a, int 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_inc(pa_atomic_int_t *a) { +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_int_t *a) { +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_int_t *a, int old_i, int new_i) { +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); } @@ -67,6 +454,8 @@ 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); } @@ -80,3 +469,5 @@ static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* n } #endif + +#endif diff --git a/src/pulsecore/authkey-prop.c b/src/pulsecore/authkey-prop.c index 7eda1e49..a953bf7d 100644 --- a/src/pulsecore/authkey-prop.c +++ b/src/pulsecore/authkey-prop.c @@ -1,86 +1,103 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General 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 <assert.h> +#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 { - int ref; + 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; - assert(c && name && data && len > 0); - + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(len > 0); + if (!(a = pa_property_get(c, name))) return -1; - assert(a->length == len); - memcpy(data, a+1, len); + 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; - assert(c && name); + + pa_assert(c); + pa_assert(name); if (pa_property_get(c, name)) return -1; - a = pa_xmalloc(sizeof(struct authkey_data) + len); - a->ref = 1; + a = pa_xmalloc(PA_ALIGN(sizeof(struct authkey_data)) + len); + PA_REFCNT_INIT(a); a->length = len; - memcpy(a+1, data, 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; - assert(c && name); - a = pa_property_get(c, name); - assert(a && a->ref >= 1); + pa_assert(c); + pa_assert(name); - a->ref++; + 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; - assert(c && name); + + pa_assert(c); + pa_assert(name); a = pa_property_get(c, name); - assert(a && a->ref >= 1); + pa_assert(a); + pa_assert(PA_REFCNT_VALUE(a) >= 1); - if (!(--a->ref)) { + 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 index b1da28be..de02d2aa 100644 --- a/src/pulsecore/authkey-prop.h +++ b/src/pulsecore/authkey-prop.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c index 87631ca5..f3f40f80 100644 --- a/src/pulsecore/authkey.c +++ b/src/pulsecore/authkey.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <unistd.h> #include <fcntl.h> #include <string.h> @@ -40,21 +40,25 @@ #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; - assert(fd >= 0 && ret_data && length); + + pa_assert(fd >= 0); + pa_assert(ret_data); + pa_assert(length > 0); pa_random(ret_data, length); lseek(fd, 0, SEEK_SET); - ftruncate(fd, 0); + (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)); + pa_log("Failed to write cookie file: %s", pa_cstrerror(errno)); return -1; } @@ -65,6 +69,10 @@ static int generate(int fd, void *ret_data, size_t length) { #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) { @@ -72,11 +80,15 @@ static int load(const char *fn, void *data, size_t length) { int writable = 1; int unlock = 0, ret = -1; ssize_t r; - assert(fn && data && length); - if ((fd = open(fn, O_RDWR|O_CREAT|O_BINARY, S_IRUSR|S_IWUSR)) < 0) { - if (errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY)) < 0) { - pa_log("failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); + 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; @@ -85,32 +97,35 @@ static int load(const char *fn, void *data, size_t length) { 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)); + 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); - + 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"); + 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); - - close(fd); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno)); + ret = -1; + } } return ret; @@ -120,13 +135,12 @@ finish: int pa_authkey_load(const char *path, void *data, size_t length) { int ret; - assert(path && data && length); + pa_assert(path); + pa_assert(data); + pa_assert(length > 0); - ret = load(path, data, length); - - if (ret < 0) - pa_log("Failed to load authorization key '%s': %s", path, - (ret == -1) ? pa_cstrerror(errno) : "file corrupt"); + 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; } @@ -134,7 +148,10 @@ int pa_authkey_load(const char *path, void *data, size_t length) { /* 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) { - assert(fn && s && l > 0); + + pa_assert(fn); + pa_assert(s); + pa_assert(l > 0); #ifndef OS_IS_WIN32 if (fn[0] != '/') { @@ -142,13 +159,14 @@ static const char *normalize_path(const char *fn, char *s, size_t l) { 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 - snprintf(s, l, "%s/%s", homedir, fn); + pa_snprintf(s, l, "%s/%s", homedir, fn); #else - snprintf(s, l, "%s\\%s", homedir, fn); + pa_snprintf(s, l, "%s\\%s", homedir, fn); #endif return s; } @@ -161,11 +179,14 @@ static const char *normalize_path(const char *fn, char *s, size_t l) { int pa_authkey_load_auto(const char *fn, void *data, size_t length) { char path[PATH_MAX]; const char *p; - assert(fn && data && length); + + 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); } @@ -176,33 +197,39 @@ int pa_authkey_save(const char *fn, const void *data, size_t length) { ssize_t r; char path[PATH_MAX]; const char *p; - assert(fn && data && length); + + 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, S_IRUSR|S_IWUSR)) < 0) { - pa_log("failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); + 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)); + 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); - - close(fd); + + 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 index cc8a565c..8301db1e 100644 --- a/src/pulsecore/authkey.h +++ b/src/pulsecore/authkey.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/autoload.c b/src/pulsecore/autoload.c index f6869097..26c294b2 100644 --- a/src/pulsecore/autoload.c +++ b/src/pulsecore/autoload.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <string.h> @@ -33,13 +33,14 @@ #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) { - assert(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); @@ -48,7 +49,8 @@ static void entry_free(pa_autoload_entry *e) { } static void entry_remove_and_free(pa_autoload_entry *e) { - assert(e && e->core); + 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); @@ -57,21 +59,23 @@ static void entry_remove_and_free(pa_autoload_entry *e) { static pa_autoload_entry* entry_new(pa_core *c, const char *name) { pa_autoload_entry *e = NULL; - assert(c && name); - + + pa_core_assert_ref(c); + pa_assert(name); + if (c->autoload_hashmap && (e = pa_hashmap_get(c->autoload_hashmap, name))) return NULL; - - e = pa_xmalloc(sizeof(pa_autoload_entry)); + + 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); - assert(c->autoload_hashmap); - + pa_assert(c->autoload_hashmap); + pa_hashmap_put(c->autoload_hashmap, e->name, e); if (!c->autoload_idxset) @@ -79,30 +83,37 @@ static pa_autoload_entry* entry_new(pa_core *c, const char *name) { 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; - assert(c && name && module && (type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE)); - + + 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; - assert(c && name && type); + + 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; @@ -113,7 +124,9 @@ int pa_autoload_remove_by_name(pa_core *c, const char*name, pa_namereg_type_t ty int pa_autoload_remove_by_index(pa_core *c, uint32_t idx) { pa_autoload_entry *e; - assert(c && idx != PA_IDXSET_INVALID); + + 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; @@ -125,7 +138,9 @@ int pa_autoload_remove_by_index(pa_core *c, uint32_t idx) { void pa_autoload_request(pa_core *c, const char *name, pa_namereg_type_t type) { pa_autoload_entry *e; pa_module *m; - assert(c && name); + + pa_assert(c); + pa_assert(name); if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || (e->type != type)) return; @@ -139,7 +154,7 @@ void pa_autoload_request(pa_core *c, const char *name, pa_namereg_type_t type) { if ((m = pa_module_load(c, e->module, e->argument))) m->auto_unload = 1; } - + e->in_action = 0; } @@ -150,11 +165,12 @@ static void free_func(void *p, PA_GCC_UNUSED void *userdata) { } 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; @@ -163,8 +179,10 @@ void pa_autoload_free(pa_core *c) { const pa_autoload_entry* pa_autoload_get_by_name(pa_core *c, const char*name, pa_namereg_type_t type) { pa_autoload_entry *e; - assert(c && name); - + + 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; @@ -173,8 +191,10 @@ const pa_autoload_entry* pa_autoload_get_by_name(pa_core *c, const char*name, pa const pa_autoload_entry* pa_autoload_get_by_index(pa_core *c, uint32_t idx) { pa_autoload_entry *e; - assert(c && idx != PA_IDXSET_INVALID); - + + 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; diff --git a/src/pulsecore/autoload.h b/src/pulsecore/autoload.h index 65bdd6da..3926351f 100644 --- a/src/pulsecore/autoload.h +++ b/src/pulsecore/autoload.h @@ -1,21 +1,21 @@ #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 @@ -35,8 +35,8 @@ typedef struct pa_autoload_entry { uint32_t index; char *name; pa_namereg_type_t type; /* Type of the autoload entry */ - int in_action; /* Currently loaded */ - char *module, *argument; + 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 diff --git a/src/pulsecore/avahi-wrap.c b/src/pulsecore/avahi-wrap.c index 80256a12..d5f40d83 100644 --- a/src/pulsecore/avahi-wrap.c +++ b/src/pulsecore/avahi-wrap.c @@ -1,29 +1,32 @@ -/* $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 <assert.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <pulse/xmalloc.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "avahi-wrap.h" @@ -58,10 +61,10 @@ static pa_io_event_flags_t translate_io_flags(AvahiWatchEvent e) { 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; - - assert(a); - assert(e); - assert(w); + + 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); @@ -72,12 +75,10 @@ static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event pa_avahi_poll *p; AvahiWatch *w; - assert(api); - assert(fd >= 0); - assert(callback); - - p = api->userdata; - assert(p); + 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; @@ -88,21 +89,21 @@ static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event return w; } - + static void watch_update(AvahiWatch *w, AvahiWatchEvent event) { - assert(w); + pa_assert(w); w->avahi_poll->mainloop->io_enable(w->io_event, translate_io_flags(event)); } - + static AvahiWatchEvent watch_get_events(AvahiWatch *w) { - assert(w); + pa_assert(w); return w->current_event; } - + static void watch_free(AvahiWatch *w) { - assert(w); + pa_assert(w); w->avahi_poll->mainloop->io_free(w->io_event); pa_xfree(w); @@ -117,10 +118,10 @@ struct AvahiTimeout { static void timeout_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { AvahiTimeout *t = userdata; - - assert(a); - assert(e); - assert(t); + + pa_assert(a); + pa_assert(e); + pa_assert(t); t->callback(t, t->userdata); } @@ -129,24 +130,22 @@ static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, pa_avahi_poll *p; AvahiTimeout *t; - assert(api); - assert(callback); - - p = api->userdata; - assert(p); + 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) { - assert(t); + pa_assert(t); if (t->time_event && tv) t->avahi_poll->mainloop->time_restart(t->time_event, tv); @@ -157,9 +156,9 @@ static void timeout_update(AvahiTimeout *t, const struct timeval *tv) { t->time_event = NULL; } } - + static void timeout_free(AvahiTimeout *t) { - assert(t); + pa_assert(t); if (t->time_event) t->avahi_poll->mainloop->time_free(t->time_event); @@ -169,10 +168,10 @@ static void timeout_free(AvahiTimeout *t) { AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *m) { pa_avahi_poll *p; - assert(m); - + 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; @@ -182,16 +181,15 @@ AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *m) { 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; - assert(api); - p = api->userdata; - assert(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 index d868fed4..7d8995bb 100644 --- a/src/pulsecore/avahi-wrap.h +++ b/src/pulsecore/avahi-wrap.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index d7e4a75c..cbdb602a 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,9 +26,10 @@ #include <stdio.h> #include <string.h> -#include <assert.h> #include <stdlib.h> #include <errno.h> +#include <unistd.h> +#include <ltdl.h> #include <pulse/xmalloc.h> @@ -55,52 +57,66 @@ struct command { const char *name; - int (*proc) (pa_core *c, pa_tokenizer*t, pa_strbuf *buf, int *fail); + int (*proc) (pa_core *c, pa_tokenizer*t, pa_strbuf *buf, pa_bool_t *fail); const char *help; unsigned args; }; -#define INCLUDE_META ".include" -#define FAIL_META ".fail" -#define NOFAIL_META ".nofail" +#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, int *fail); -static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_list_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); -static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail); +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_describe(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 */ @@ -117,13 +133,15 @@ static const struct command commands[] = { { "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|name, volume)", 3}, + { "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}, + { "describe-module", pa_cli_command_describe, "Describe a module (arg: name)", 2}, + { "set-sink-volume", pa_cli_command_sink_volume, "Set the volume of a sink (args: index|name, volume)", 3}, + { "set-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, mute)", 3}, - { "set-source-mute", pa_cli_command_source_mute, "Set the mute switch of a source (args: index|name, mute)", 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}, @@ -137,15 +155,18 @@ static const struct command commands[] = { { "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}, + { "add-autoload-sink", pa_cli_command_autoload_add, NULL /*"Add autoload entry for a sink (args: sink, module name, arguments)"*/, 4}, + { "add-autoload-source", pa_cli_command_autoload_add, NULL /*"Add autoload entry for a source (args: source, module name, arguments)"*/, 4}, + { "remove-autoload-sink", pa_cli_command_autoload_remove, NULL /*"Remove autoload entry for a sink (args: name)"*/, 2}, + { "remove-autoload-source", pa_cli_command_autoload_remove, NULL /*"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 } }; @@ -161,88 +182,121 @@ static uint32_t parse_index(const char *n) { return idx; } -static int pa_cli_command_exit(pa_core *c, pa_tokenizer *t, PA_GCC_UNUSED pa_strbuf *buf, PA_GCC_UNUSED int *fail) { - assert(c && c->mainloop && t); +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_GCC_UNUSED int *fail) { +static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { const struct command*command; - assert(c && t && buf); + + 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_GCC_UNUSED int *fail) { +static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_module_list_to_string(c); - assert(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_GCC_UNUSED int *fail) { +static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_client_list_to_string(c); - assert(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_GCC_UNUSED int *fail) { +static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_sink_list_to_string(c); - assert(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_GCC_UNUSED int *fail) { +static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_source_list_to_string(c); - assert(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_GCC_UNUSED int *fail) { +static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_sink_input_list_to_string(c); - assert(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_GCC_UNUSED int *fail) { +static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_source_output_list_to_string(c); - assert(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_GCC_UNUSED int *fail) { +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", @@ -252,12 +306,14 @@ static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G [PA_MEMBLOCK_FIXED] = "FIXED", [PA_MEMBLOCK_IMPORTED] = "IMPORTED", }; - - assert(c); - assert(t); + + 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))); @@ -280,10 +336,12 @@ static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G 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", - pa_namereg_get_default_sink_name(c), - pa_namereg_get_default_source_name(c)); + def_sink ? def_sink : "none", + def_source ? def_source : "none"); for (k = 0; k < PA_MEMBLOCK_TYPE_MAX; k++) pa_strbuf_printf(buf, @@ -291,12 +349,16 @@ static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G 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, int *fail) { - assert(c && t); +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); @@ -305,20 +367,24 @@ static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int 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); +/* 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_GCC_UNUSED int *fail) { +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; - assert(c && t); + + 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; @@ -327,12 +393,16 @@ static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G return 0; } -static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +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; - assert(c && t); + + 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"); @@ -349,19 +419,63 @@ static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA return 0; } -static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *name; + pa_modinfo *i; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(name = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify the module name.\n"); + return -1; + } + + if ((i = pa_modinfo_get_by_name(name))) { + + pa_strbuf_printf(buf, "Name: %s\n", name); + + if (!i->description && !i->version && !i->author && !i->usage) + pa_strbuf_printf(buf, "No module information available\n"); + else { + if (i->version) + pa_strbuf_printf(buf, "Version: %s\n", i->version); + if (i->description) + pa_strbuf_printf(buf, "Description: %s\n", i->description); + if (i->author) + pa_strbuf_printf(buf, "Author: %s\n", i->author); + if (i->usage) + pa_strbuf_printf(buf, "Usage: %s\n", i->usage); + pa_strbuf_printf(buf, "Load Once: %s\n", pa_yes_no(i->load_once)); + } + + pa_modinfo_free(i); + } else + pa_strbuf_puts(buf, "Failed to open module.\n"); + + return 0; +} + +static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, 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"); + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n"); return -1; } @@ -376,17 +490,22 @@ static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *bu } pa_cvolume_set(&cvolume, sink->sample_spec.channels, volume); - pa_sink_set_volume(sink, PA_MIXER_HARDWARE, &cvolume); + 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_GCC_UNUSED int *fail) { +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; @@ -398,7 +517,7 @@ static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strb } 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"); + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n"); return -1; } @@ -417,19 +536,24 @@ static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strb return 0; } -static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +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"); + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n"); return -1; } @@ -444,15 +568,20 @@ static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf * } pa_cvolume_set(&cvolume, source->sample_spec.channels, volume); - pa_source_set_volume(source, PA_MIXER_HARDWARE, &cvolume); + 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_GCC_UNUSED int *fail) { +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; @@ -463,7 +592,7 @@ static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, return -1; } - if (pa_atoi(m, &mute) < 0) { + if ((mute = pa_parse_boolean(m)) < 0) { pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); return -1; } @@ -473,15 +602,20 @@ static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, return -1; } - pa_sink_set_mute(sink, PA_MIXER_HARDWARE, mute); + 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_GCC_UNUSED int *fail) { +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; @@ -492,7 +626,7 @@ static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *bu return -1; } - if (pa_atoi(m, &mute) < 0) { + if ((mute = pa_parse_boolean(m)) < 0) { pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); return -1; } @@ -502,13 +636,57 @@ static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *bu return -1; } - pa_source_set_mute(source, PA_MIXER_HARDWARE, mute); + 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 mute switch setting (0/1).\n"); + return -1; + } + + if ((mute = pa_parse_boolean(v)) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + pa_sink_input_set_mute(si, mute); return 0; } -static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { const char *n; - assert(c && t); + + 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"); @@ -519,9 +697,13 @@ static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *b return 0; } -static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { const char *n; - assert(c && t); + + 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"); @@ -532,11 +714,15 @@ static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf return 0; } -static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +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; - assert(c && t); + + 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"); @@ -557,11 +743,15 @@ static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *bu return 0; } -static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +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; - assert(c && t); + + 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"); @@ -582,11 +772,15 @@ static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf return 0; } -static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +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; - assert(c && t); + + 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"); @@ -607,20 +801,30 @@ static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_str return 0; } -static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_scache_list_to_string(c); - assert(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, int *fail) { +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; - assert(c && t && buf && fail); + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); if (!(n = pa_tokenizer_get(t, 1)) || !(sink_name = pa_tokenizer_get(t, 2))) { pa_strbuf_puts(buf, "You need to specify a sample name and a sink name.\n"); @@ -632,17 +836,23 @@ static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *bu return -1; } - if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM) < 0) { + if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM, NULL, &idx) < 0) { pa_strbuf_puts(buf, "Failed to play sample.\n"); return -1; } + pa_strbuf_printf(buf, "Playing on sink input #%i\n", idx); + return 0; } -static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail) { +static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { const char *n; - assert(c && t && buf && fail); + + 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"); @@ -657,10 +867,14 @@ static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf * return 0; } -static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail) { +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; - assert(c && t && buf && fail); + + 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"); @@ -678,9 +892,13 @@ static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *bu return 0; } -static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail) { +static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { const char *pname; - assert(c && t && buf && fail); + + 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"); @@ -695,10 +913,14 @@ static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf return 0; } -static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail) { +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; - assert(c && t && buf && fail); + + 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"); @@ -714,9 +936,15 @@ static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, return pa_play_file(sink, fname, NULL); } -static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, int *fail) { +static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { const char *a, *b; - assert(c && t && buf && fail); + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_log_warn("Autoload will no longer be implemented by future versions of the PulseAudio server."); 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"); @@ -724,14 +952,20 @@ static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *b } 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, int *fail) { +static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { const char *name; - assert(c && t && buf && fail); - + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_log_warn("Autoload will no longer be implemented by future versions of the PulseAudio server."); + if (!(name = pa_tokenizer_get(t, 1))) { pa_strbuf_puts(buf, "You need to specify a device name\n"); return -1; @@ -742,40 +976,58 @@ static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf return -1; } - return 0; + return 0; } -static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { char *s; - assert(c && t); - s = pa_autoload_list_to_string(c); - assert(s); + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_log_warn("Autoload will no longer be implemented by future versions of the PulseAudio server."); + + 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_GCC_UNUSED int *fail) { - assert(c && t); +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, int *fail) { - assert(c); - assert(t); +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, int *fail) { +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; @@ -801,19 +1053,24 @@ static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf return -1; } - if (pa_sink_input_move_to(si, sink, 0) < 0) { + if (pa_sink_input_move_to(si, sink) < 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, int *fail) { +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; @@ -846,7 +1103,105 @@ static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_str return 0; } -static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_GCC_UNUSED int *fail) { +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 ((suspend = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 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 ((suspend = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 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 ((suspend = pa_parse_boolean(m)) < 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; @@ -857,8 +1212,11 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G time_t now; void *i; pa_autoload_entry *a; - - assert(c && t); + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); time(&now); @@ -868,7 +1226,6 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G 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; @@ -884,7 +1241,7 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G nl = 0; for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) { - if (sink->owner && sink->owner->auto_unload) + if (sink->module && sink->module->auto_unload) continue; if (!nl) { @@ -892,12 +1249,13 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G nl = 1; } - pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, PA_MIXER_HARDWARE))); - pa_strbuf_printf(buf, "set-sink-mute %s %d\n", sink->name, pa_sink_get_mute(sink, PA_MIXER_HARDWARE)); + 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 %s\n", sink->name, pa_yes_no(pa_sink_get_mute(sink))); + pa_strbuf_printf(buf, "suspend-sink %s %s\n", sink->name, pa_yes_no(pa_sink_get_state(sink) == PA_SINK_SUSPENDED)); } for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) { - if (source->owner && source->owner->auto_unload) + if (source->module && source->module->auto_unload) continue; if (!nl) { @@ -905,14 +1263,15 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G nl = 1; } - pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_avg(pa_source_get_volume(source, PA_MIXER_HARDWARE))); - pa_strbuf_printf(buf, "set-source-mute %s %d\n", source->name, pa_source_get_mute(source, PA_MIXER_HARDWARE)); + 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 %s\n", source->name, pa_yes_no(pa_source_get_mute(source))); + pa_strbuf_printf(buf, "suspend-source %s %s\n", source->name, pa_yes_no(pa_source_get_state(source) == PA_SOURCE_SUSPENDED)); } if (c->autoload_hashmap) { nl = 0; - + i = NULL; while ((a = pa_hashmap_iterate(c->autoload_hashmap, &i, NULL))) { @@ -920,18 +1279,18 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G 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"); @@ -953,27 +1312,90 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, PA_G return 0; } -int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, int *fail) { +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, FAIL_META)) - *fail = 1; - else if (!strcmp(cs, NOFAIL_META)) - *fail = 0; + 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(INCLUDE_META)-1 && !strncmp(cs, INCLUDE_META, l)) { + 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; + 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); + + /* Search DL_SEARCH_PATH unless the filename is absolute */ + if (filename[0] == PA_PATH_SEP_CHAR) { + + *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 { + const char *paths, *state = NULL; + char *p; + + if (!(paths = lt_dlgetsearchpath())) + return -1; + + while ((p = pa_split(paths, ":", &state))) { + char *pathname; + + pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", p, filename); + pa_xfree(p); + + *ifstate = access(pathname, F_OK) == 0 ? IFSTATE_TRUE : IFSTATE_FALSE; + pa_log_debug("Checking for existance of '%s': %s", pathname, *ifstate == IFSTATE_TRUE ? "success" : "failure"); + + pa_xfree(pathname); + + if (*ifstate == IFSTATE_TRUE) + break; + } + } + + } } else { pa_strbuf_printf(buf, "Invalid meta command: %s\n", cs); if (*fail) return -1; @@ -983,21 +1405,24 @@ int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, int * 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++) + 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); - assert(t); + pa_assert(t); ret = command->proc(c, t, buf, fail); pa_tokenizer_free(t); unknown = 0; if (ret < 0 && *fail) return -1; - + break; } @@ -1011,11 +1436,48 @@ int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, int * return 0; } -int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, int *fail) { - char line[256]; +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_stream(pa_core *c, FILE *f, pa_strbuf *buf, pa_bool_t *fail) { + char line[1024]; + int ifstate = IFSTATE_NONE; + int ret = -1; + pa_bool_t _fail = TRUE; + + pa_assert(c); + pa_assert(f); + pa_assert(buf); + + if (!fail) + fail = &_fail; + + while (fgets(line, sizeof(line), f)) { + pa_strip_nl(line); + + if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail) + goto fail; + } + + ret = 0; + +fail: + + return ret; +} + +int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *fail) { FILE *f = NULL; int ret = -1; - assert(c && fn && buf); + pa_bool_t _fail = TRUE; + + pa_assert(c); + pa_assert(fn); + pa_assert(buf); + + if (!fail) + fail = &_fail; if (!(f = fopen(fn, "r"))) { pa_strbuf_printf(buf, "open('%s') failed: %s\n", fn, pa_cstrerror(errno)); @@ -1024,13 +1486,7 @@ int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, int goto fail; } - while (fgets(line, sizeof(line), f)) { - char *e = line + strcspn(line, linebreak); - *e = 0; - - if (pa_cli_command_execute_line(c, line, buf, fail) < 0 && *fail) - goto fail; - } + ret = pa_cli_command_execute_file_stream(c, f, buf, fail); ret = 0; @@ -1041,16 +1497,24 @@ fail: return ret; } -int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, int *fail) { +int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail) { const char *p; - assert(c && s && buf && fail); + int ifstate = IFSTATE_NONE; + pa_bool_t _fail = TRUE; + + pa_assert(c); + pa_assert(s); + pa_assert(buf); + + if (!fail) + fail = &_fail; p = s; while (*p) { size_t l = strcspn(p, linebreak); char *line = pa_xstrndup(p, l); - - if (pa_cli_command_execute_line(c, line, buf, fail) < 0&& *fail) { + + if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail) { pa_xfree(line); return -1; } diff --git a/src/pulsecore/cli-command.h b/src/pulsecore/cli-command.h index c56c3ca0..9bf35dc3 100644 --- a/src/pulsecore/cli-command.h +++ b/src/pulsecore/cli-command.h @@ -1,21 +1,21 @@ #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 @@ -29,12 +29,18 @@ * 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, int *fail); +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); /* Execute a whole file of CLI commands */ -int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, int *fail); +int pa_cli_command_execute_file_stream(pa_core *c, FILE *f, 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, int *fail); +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 index 49934c07..c92fca20 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,11 +23,11 @@ #include <config.h> #endif -#include <assert.h> #include <string.h> #include <pulse/volume.h> #include <pulse/xmalloc.h> +#include <pulse/timeval.h> #include <pulsecore/module.h> #include <pulsecore/client.h> @@ -39,6 +39,8 @@ #include <pulsecore/sample-util.h> #include <pulsecore/core-scache.h> #include <pulsecore/autoload.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> #include "cli-text.h" @@ -46,16 +48,22 @@ char *pa_module_list_to_string(pa_core *c) { pa_strbuf *s; pa_module *m; uint32_t idx = PA_IDXSET_INVALID; - assert(c); + pa_assert(c); s = pa_strbuf_new(); - assert(s); 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->n_used, m->auto_unload ? "yes" : "no"); - + + 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, + pa_yes_no(m->auto_unload)); + } + return pa_strbuf_tostring_free(s); } @@ -63,20 +71,29 @@ char *pa_client_list_to_string(pa_core *c) { pa_strbuf *s; pa_client *client; uint32_t idx = PA_IDXSET_INVALID; - assert(c); + pa_assert(c); s = pa_strbuf_new(); - assert(s); 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); + char *t; + pa_strbuf_printf( + s, + " index: %u\n" + "\tdriver: <%s>\n", + client->index, + client->driver); + + if (client->module) + pa_strbuf_printf(s, "\towner module: %u\n", client->module->index); - if (client->owner) - pa_strbuf_printf(s, "\towner module: <%u>\n", client->owner->index); + t = pa_proplist_to_string(client->proplist); + pa_strbuf_printf(s, "\tproperties:\n%s", t); + pa_xfree(t); } - + return pa_strbuf_tostring_free(s); } @@ -84,41 +101,74 @@ char *pa_sink_list_to_string(pa_core *c) { pa_strbuf *s; pa_sink *sink; uint32_t idx = PA_IDXSET_INVALID; - assert(c); + static const char* const state_table[] = { + [PA_SINK_INIT] = "INIT", + [PA_SINK_RUNNING] = "RUNNING", + [PA_SINK_SUSPENDED] = "SUSPENDED", + [PA_SINK_IDLE] = "IDLE", + [PA_SINK_UNLINKED] = "UNLINKED" + }; + pa_assert(c); s = pa_strbuf_new(); - assert(s); 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]; - + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t; + pa_usec_t min_latency, max_latency; + + pa_sink_get_latency_range(sink, &min_latency, &max_latency); + pa_strbuf_printf( s, " %c index: %u\n" "\tname: <%s>\n" "\tdriver: <%s>\n" - "\tvolume: <%s>\n" - "\tlatency: <%0.0f usec>\n" - "\tmonitor_source: <%u>\n" - "\tsample spec: <%s>\n" - "\tchannel map: <%s>\n", + "\tflags: %s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tvolume: %s\n" + "\tmuted: %s\n" + "\tcurrent latency: %0.2f ms\n" + "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n" + "\tmax request: %lu KiB\n" + "\tmax rewind: %lu KiB\n" + "\tmonitor source: %u\n" + "\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->index, + sink->name, sink->driver, - pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, PA_MIXER_HARDWARE)), - (double) pa_sink_get_latency(sink), + sink->flags & PA_SINK_HARDWARE ? "HARDWARE " : "", + sink->flags & PA_SINK_NETWORK ? "NETWORK " : "", + sink->flags & PA_SINK_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", + sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + sink->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", + sink->flags & PA_SINK_LATENCY ? "LATENCY " : "", + state_table[pa_sink_get_state(sink)], + pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink)), + pa_yes_no(pa_sink_get_mute(sink)), + (double) pa_sink_get_latency(sink) / PA_USEC_PER_MSEC, + (double) pa_sink_get_requested_latency(sink) / PA_USEC_PER_MSEC, (double) min_latency / PA_USEC_PER_MSEC, (double) max_latency / PA_USEC_PER_MSEC, + (unsigned long) pa_sink_get_max_request(sink) / 1024, + (unsigned long) pa_sink_get_max_rewind(sink) / 1024, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, pa_sample_spec_snprint(ss, sizeof(ss), &sink->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map)); + pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map), + pa_sink_used_by(sink), + pa_sink_linked_by(sink)); - if (sink->owner) - pa_strbuf_printf(s, "\towner module: <%u>\n", sink->owner->index); - if (sink->description) - pa_strbuf_printf(s, "\tdescription: <%s>\n", sink->description); + if (sink->module) + pa_strbuf_printf(s, "\tmodule: %u\n", sink->module->index); + + t = pa_proplist_to_string(sink->proplist); + pa_strbuf_printf(s, "\tproperties:\n%s", t); + pa_xfree(t); } - + return pa_strbuf_tostring_free(s); } @@ -126,41 +176,72 @@ char *pa_source_list_to_string(pa_core *c) { pa_strbuf *s; pa_source *source; uint32_t idx = PA_IDXSET_INVALID; - assert(c); + static const char* const state_table[] = { + [PA_SOURCE_INIT] = "INIT", + [PA_SOURCE_RUNNING] = "RUNNING", + [PA_SOURCE_SUSPENDED] = "SUSPENDED", + [PA_SOURCE_IDLE] = "IDLE", + [PA_SOURCE_UNLINKED] = "UNLINKED" + }; + pa_assert(c); s = pa_strbuf_new(); - assert(s); 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]; - - + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], *t; + pa_usec_t min_latency, max_latency; + + pa_source_get_latency_range(source, &min_latency, &max_latency); + pa_strbuf_printf( s, " %c index: %u\n" "\tname: <%s>\n" "\tdriver: <%s>\n" - "\tlatency: <%0.0f usec>\n" - "\tsample spec: <%s>\n" - "\tchannel map: <%s>\n", + "\tflags: %s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tvolume: %s\n" + "\tmuted: %s\n" + "\tcurrent latency: %0.2f ms\n" + "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n" + "\tmax rewind: %lu KiB\n" + "\tsample spec: %s\n" + "\tchannel map: %s\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, - (double) pa_source_get_latency(source), + source->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "", + source->flags & PA_SOURCE_NETWORK ? "NETWORK " : "", + source->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", + source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + source->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", + source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", + state_table[pa_source_get_state(source)], + pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source)), + pa_yes_no(pa_source_get_mute(source)), + (double) pa_source_get_latency(source) / PA_USEC_PER_MSEC, + (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, (double) min_latency / PA_USEC_PER_MSEC, (double) max_latency / PA_USEC_PER_MSEC, + (unsigned long) pa_source_get_max_rewind(source) / 1024, pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map)); - - if (source->monitor_of) - pa_strbuf_printf(s, "\tmonitor_of: <%u>\n", source->monitor_of->index); - if (source->owner) - pa_strbuf_printf(s, "\towner module: <%u>\n", source->owner->index); - if (source->description) - pa_strbuf_printf(s, "\tdescription: <%s>\n", source->description); + 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); + + t = pa_proplist_to_string(source->proplist); + pa_strbuf_printf(s, "\tproperties:\n%s", t); + pa_xfree(t); } - + return pa_strbuf_tostring_free(s); } @@ -170,46 +251,69 @@ char *pa_source_output_list_to_string(pa_core *c) { pa_source_output *o; uint32_t idx = PA_IDXSET_INVALID; static const char* const state_table[] = { - "RUNNING", - "CORKED", - "DISCONNECTED" + [PA_SOURCE_OUTPUT_INIT] = "INIT", + [PA_SOURCE_OUTPUT_RUNNING] = "RUNNING", + [PA_SOURCE_OUTPUT_CORKED] = "CORKED", + [PA_SOURCE_OUTPUT_UNLINKED] = "UNLINKED" }; - assert(c); + pa_assert(c); s = pa_strbuf_new(); - assert(s); 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]; - - assert(o->source); - + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28]; + pa_usec_t cl; + + if ((cl = pa_source_output_get_requested_latency(o)) == (pa_usec_t) -1) + pa_snprintf(clt, sizeof(clt), "n/a"); + else + pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC); + + pa_assert(o->source); + pa_strbuf_printf( s, " index: %u\n" - "\tname: '%s'\n" "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s%s\n" "\tstate: %s\n" - "\tsource: <%u> '%s'\n" - "\tsample spec: <%s>\n" - "\tchannel map: <%s>\n" + "\tsource: %u <%s>\n" + "\tcurrent latency: %0.2f ms\n" + "\trequested latency: %s\n" + "\tsample spec: %s\n" + "\tchannel map: %s\n" "\tresample method: %s\n", o->index, - o->name, o->driver, - state_table[o->state], + o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "", + o->flags & PA_SOURCE_OUTPUT_DONT_MOVE ? "DONT_MOVE " : "", + o->flags & PA_SOURCE_OUTPUT_START_CORKED ? "START_CORKED " : "", + o->flags & PA_SOURCE_OUTPUT_NO_REMAP ? "NO_REMAP " : "", + o->flags & PA_SOURCE_OUTPUT_NO_REMIX ? "NO_REMIX " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_FORMAT ? "FIX_FORMAT " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_RATE ? "FIX_RATE " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", + state_table[pa_source_output_get_state(o)], o->source->index, o->source->name, + (double) pa_source_output_get_latency(o, NULL) / PA_USEC_PER_MSEC, + clt, pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec), pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map), 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); + 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); + pa_strbuf_printf(s, "\tclient: %u <%s>\n", o->client->index, pa_strnull(pa_proplist_gets(o->client->proplist, PA_PROP_APPLICATION_NAME))); + if (o->direct_on_input) + pa_strbuf_printf(s, "\tdirect on input: %u\n", o->direct_on_input->index); + + t = pa_proplist_to_string(o->proplist); + pa_strbuf_printf(s, "\tproperties:\n%s", t); + pa_xfree(t); } - + return pa_strbuf_tostring_free(s); } @@ -218,60 +322,81 @@ char *pa_sink_input_list_to_string(pa_core *c) { pa_sink_input *i; uint32_t idx = PA_IDXSET_INVALID; static const char* const state_table[] = { - "RUNNING", - "CORKED", - "DISCONNECTED" + [PA_SINK_INPUT_INIT] = "INIT", + [PA_SINK_INPUT_RUNNING] = "RUNNING", + [PA_SINK_INPUT_DRAINED] = "DRAINED", + [PA_SINK_INPUT_CORKED] = "CORKED", + [PA_SINK_INPUT_UNLINKED] = "UNLINKED" }; - assert(c); + pa_assert(c); s = pa_strbuf_new(); - assert(s); 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]; + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28]; + pa_usec_t cl; + + if ((cl = pa_sink_input_get_requested_latency(i)) == (pa_usec_t) -1) + pa_snprintf(clt, sizeof(clt), "n/a"); + else + pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC); + + pa_assert(i->sink); - assert(i->sink); - pa_strbuf_printf( s, " index: %u\n" - "\tname: <%s>\n" "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s%s\n" "\tstate: %s\n" - "\tsink: <%u> '%s'\n" - "\tvolume: <%s>\n" - "\tlatency: <%0.0f usec>\n" - "\tsample spec: <%s>\n" - "\tchannel map: <%s>\n" + "\tsink: %u <%s>\n" + "\tvolume: %s\n" + "\tmuted: %s\n" + "\tcurrent latency: %0.2f ms\n" + "\trequested latency: %s\n" + "\tsample spec: %s\n" + "\tchannel map: %s\n" "\tresample method: %s\n", i->index, - i->name, i->driver, - state_table[i->state], + i->flags & PA_SINK_INPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "", + i->flags & PA_SINK_INPUT_DONT_MOVE ? "DONT_MOVE " : "", + i->flags & PA_SINK_INPUT_START_CORKED ? "START_CORKED " : "", + i->flags & PA_SINK_INPUT_NO_REMAP ? "NO_REMAP " : "", + i->flags & PA_SINK_INPUT_NO_REMIX ? "NO_REMIX " : "", + i->flags & PA_SINK_INPUT_FIX_FORMAT ? "FIX_FORMAT " : "", + i->flags & PA_SINK_INPUT_FIX_RATE ? "FIX_RATE " : "", + i->flags & PA_SINK_INPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", + 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)), - (double) pa_sink_input_get_latency(i), + pa_yes_no(pa_sink_input_get_mute(i)), + (double) pa_sink_input_get_latency(i, NULL) / PA_USEC_PER_MSEC, + clt, pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec), pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), pa_resample_method_to_string(pa_sink_input_get_resample_method(i))); if (i->module) - pa_strbuf_printf(s, "\towner module: <%u>\n", i->module->index); + 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); + pa_strbuf_printf(s, "\tclient: %u <%s>\n", i->client->index, pa_strnull(pa_proplist_gets(i->client->proplist, PA_PROP_APPLICATION_NAME))); + + t = pa_proplist_to_string(i->proplist); + pa_strbuf_printf(s, "\tproperties:\n%s", t); + pa_xfree(t); } - + return pa_strbuf_tostring_free(s); } char *pa_scache_list_to_string(pa_core *c) { pa_strbuf *s; - assert(c); + pa_assert(c); s = pa_strbuf_new(); - assert(s); pa_strbuf_printf(s, "%u cache entries available.\n", c->scache ? pa_idxset_size(c->scache) : 0); @@ -281,25 +406,25 @@ char *pa_scache_list_to_string(pa_core *c) { 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"; - + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = "n/a", cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX] = "n/a", *t; + 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" + "\tindex: %u\n" + "\tsample spec: %s\n" + "\tchannel map: %s\n" + "\tlength: %lu\n" + "\tduration: %0.1f s\n" + "\tvolume: %s\n" "\tlazy: %s\n" - "\tfilename: %s\n", + "\tfilename: <%s>\n", e->name, e->index, ss, @@ -307,8 +432,12 @@ char *pa_scache_list_to_string(pa_core *c) { (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0), l, pa_cvolume_snprint(cv, sizeof(cv), &e->volume), - e->lazy ? "yes" : "no", + pa_yes_no(e->lazy), e->filename ? e->filename : "n/a"); + + t = pa_proplist_to_string(e->proplist); + pa_strbuf_printf(s, "\tproperties:\n%s", t); + pa_xfree(t); } } @@ -317,10 +446,9 @@ char *pa_scache_list_to_string(pa_core *c) { char *pa_autoload_list_to_string(pa_core *c) { pa_strbuf *s; - assert(c); + pa_assert(c); s = pa_strbuf_new(); - assert(s); pa_strbuf_printf(s, "%u autoload entries available.\n", c->autoload_hashmap ? pa_hashmap_size(c->autoload_hashmap) : 0); @@ -330,12 +458,17 @@ char *pa_autoload_list_to_string(pa_core *c) { 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", + 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 ? e->argument : ""); } } @@ -351,9 +484,9 @@ char *pa_full_status_string(pa_core *c) { for (i = 0; i < 8; i++) { char *t = NULL; - + switch (i) { - case 0: + case 0: t = pa_sink_list_to_string(c); break; case 1: @@ -365,7 +498,7 @@ char *pa_full_status_string(pa_core *c) { case 3: t = pa_source_output_list_to_string(c); break; - case 4: + case 4: t = pa_client_list_to_string(c); break; case 5: diff --git a/src/pulsecore/cli-text.h b/src/pulsecore/cli-text.h index cd3acdee..f4cb97a5 100644 --- a/src/pulsecore/cli-text.h +++ b/src/pulsecore/cli-text.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c index e3fc2e4c..b3c639f8 100644 --- a/src/pulsecore/cli.c +++ b/src/pulsecore/cli.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -25,7 +25,6 @@ #include <stdio.h> #include <string.h> -#include <assert.h> #include <stdlib.h> #include <pulse/xmalloc.h> @@ -43,6 +42,7 @@ #include <pulsecore/cli-text.h> #include <pulsecore/cli-command.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "cli.h" @@ -57,7 +57,8 @@ struct pa_cli { pa_client *client; - int fail, kill_requested, defer_kill; + pa_bool_t fail, kill_requested; + int defer_kill; }; static void line_callback(pa_ioline *line, const char *s, void *userdata); @@ -66,33 +67,33 @@ 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; - assert(io); + pa_assert(io); - c = pa_xmalloc(sizeof(pa_cli)); + c = pa_xnew(pa_cli, 1); c->core = core; - c->line = pa_ioline_new(io); - assert(c->line); + 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)); - c->client = pa_client_new(core, __FILE__, cname); - assert(c->client); + pa_assert_se(c->client = pa_client_new(core, __FILE__, cname)); c->client->kill = client_kill; c->client->userdata = c; - c->client->owner = m; - + c->client->module = 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 = c->defer_kill = 0; - + c->fail = c->kill_requested = FALSE; + c->defer_kill = 0; + return c; } void pa_cli_free(pa_cli *c) { - assert(c); + pa_assert(c); + pa_ioline_close(c->line); pa_ioline_unref(c->line); pa_client_free(c->client); @@ -101,12 +102,13 @@ void pa_cli_free(pa_cli *c) { static void client_kill(pa_client *client) { pa_cli *c; - assert(client && client->userdata); - c = client->userdata; - + + pa_assert(client); + pa_assert_se(c = client->userdata); + pa_log_debug("CLI client killed."); if (c->defer_kill) - c->kill_requested = 1; + c->kill_requested = TRUE; else { if (c->eof_callback) c->eof_callback(c, c->userdata); @@ -117,7 +119,9 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { pa_strbuf *buf; pa_cli *c = userdata; char *p; - assert(line && c); + + pa_assert(line); + pa_assert(c); if (!s) { pa_log_debug("CLI got EOF from user."); @@ -127,8 +131,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { return; } - buf = pa_strbuf_new(); - assert(buf); + pa_assert_se(buf = pa_strbuf_new()); c->defer_kill++; pa_cli_command_execute_line(c->core, s, buf, &c->fail); c->defer_kill--; @@ -138,12 +141,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { if (c->kill_requested) { if (c->eof_callback) c->eof_callback(c, c->userdata); - } else + } else pa_ioline_puts(line, PROMPT); } void pa_cli_set_eof_callback(pa_cli *c, void (*cb)(pa_cli*c, void *userdata), void *userdata) { - assert(c); + pa_assert(c); + c->eof_callback = cb; c->userdata = userdata; } diff --git a/src/pulsecore/cli.h b/src/pulsecore/cli.h index 639fa952..6077a8e8 100644 --- a/src/pulsecore/cli.h +++ b/src/pulsecore/cli.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/client.c b/src/pulsecore/client.c index c34bf149..0ffd2330 100644 --- a/src/pulsecore/client.c +++ b/src/pulsecore/client.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,7 +25,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> @@ -32,50 +32,58 @@ #include <pulsecore/core-subscribe.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> #include "client.h" pa_client *pa_client_new(pa_core *core, const char *driver, const char *name) { pa_client *c; - int r; - assert(core); - c = pa_xmalloc(sizeof(pa_client)); - c->name = pa_xstrdup(name); - c->driver = pa_xstrdup(driver); - c->owner = NULL; + pa_core_assert_ref(core); + + c = pa_xnew(pa_client, 1); c->core = core; + c->proplist = pa_proplist_new(); + if (name) + pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name); + c->driver = pa_xstrdup(driver); + c->module = NULL; c->kill = NULL; c->userdata = NULL; - r = pa_idxset_put(core->clients, c, &c->index); - assert(c->index != PA_IDXSET_INVALID && r >= 0); + pa_assert_se(pa_idxset_put(core->clients, c, &c->index) >= 0); - pa_log_info("created %u \"%s\"", c->index, c->name); + pa_log_info("Created %u \"%s\"", c->index, pa_strnull(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) { - assert(c && c->core); + pa_core *core; - pa_idxset_remove_by_data(c->core->clients, c, NULL); + pa_assert(c); + pa_assert(c->core); - pa_core_check_quit(c->core); + core = c->core; + pa_idxset_remove_by_data(c->core->clients, c, NULL); - pa_log_info("freed %u \"%s\"", c->index, c->name); + pa_log_info("Freed %u \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME))); pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE, c->index); - pa_xfree(c->name); + pa_proplist_free(c->proplist); pa_xfree(c->driver); pa_xfree(c); + + pa_core_check_quit(core); } void pa_client_kill(pa_client *c) { - assert(c); + pa_assert(c); + if (!c->kill) { pa_log_warn("kill() operation not implemented for client %u", c->index); return; @@ -85,12 +93,9 @@ void pa_client_kill(pa_client *c) { } void pa_client_set_name(pa_client *c, const char *name) { - 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_assert(c); + pa_log_info("Client %u changed name from \"%s\" to \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)), name); + pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name); pa_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 index b28065e5..28d1fe5f 100644 --- a/src/pulsecore/client.h +++ b/src/pulsecore/client.h @@ -1,21 +1,21 @@ #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 @@ -26,6 +26,7 @@ typedef struct pa_client pa_client; +#include <pulse/proplist.h> #include <pulsecore/core.h> #include <pulsecore/module.h> @@ -35,11 +36,12 @@ typedef struct pa_client pa_client; struct pa_client { uint32_t index; - - pa_module *owner; - char *name, *driver; pa_core *core; + pa_proplist *proplist; + pa_module *module; + char *driver; + void (*kill)(pa_client *c); void *userdata; }; diff --git a/src/pulsecore/conf-parser.c b/src/pulsecore/conf-parser.c index db1e3719..4aec45d7 100644 --- a/src/pulsecore/conf-parser.c +++ b/src/pulsecore/conf-parser.c @@ -1,8 +1,8 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <string.h> #include <stdio.h> #include <errno.h> @@ -33,6 +32,7 @@ #include <pulsecore/core-error.h> #include <pulsecore/log.h> #include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "conf-parser.h" @@ -41,25 +41,28 @@ /* 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) { - assert(filename && t && lvalue && rvalue); - + 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) { - assert(s); - + pa_assert(s); + for (; *s; s++) if (*s == c) return 1; - + return 0; } @@ -85,7 +88,7 @@ static int parse_line(const char *filename, unsigned line, const pa_config_item if ((c = strpbrk(b, COMMENTS))) *c = 0; - + if (!*b) return 0; @@ -105,15 +108,17 @@ int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void int r = -1; unsigned line = 0; int do_close = !f; - assert(filename && t); - + + pa_assert(filename); + pa_assert(t); + if (!f && !(f = fopen(filename, "r"))) { if (errno == ENOENT) { r = 0; goto finish; } - - pa_log_warn("WARNING: failed to open configuration file '%s': %s", + + pa_log_warn("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno)); goto finish; } @@ -123,57 +128,70 @@ int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void if (!fgets(l, sizeof(l), f)) { if (feof(f)) break; - - pa_log_warn("WARNING: failed to read configuration file '%s': %s", + + 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; - assert(filename && lvalue && rvalue && data); + + 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; + 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 *b = data, k; - assert(filename && lvalue && rvalue && data); - + 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; - + + *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; - assert(filename && lvalue && rvalue && data); + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); pa_xfree(*s); *s = *rvalue ? pa_xstrdup(rvalue) : NULL; diff --git a/src/pulsecore/conf-parser.h b/src/pulsecore/conf-parser.h index 9c1a697a..7eb1fae2 100644 --- a/src/pulsecore/conf-parser.h +++ b/src/pulsecore/conf-parser.h @@ -1,11 +1,11 @@ #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, diff --git a/src/pulsecore/core-error.c b/src/pulsecore/core-error.c index 61878c9e..3d6c2c3b 100644 --- a/src/pulsecore/core-error.c +++ b/src/pulsecore/core-error.c @@ -1,18 +1,19 @@ -/* $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 @@ -28,178 +29,50 @@ #include <stdlib.h> #include <string.h> -#ifdef HAVE_PTHREAD -#include <pthread.h> -#endif - -#ifdef HAVE_WINDOWS_H -#include <windows.h> -#endif - #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" -#ifdef HAVE_PTHREAD - -static pthread_once_t cstrerror_once = PTHREAD_ONCE_INIT; -static pthread_key_t tlsstr_key; - -static void inittls(void) { - int ret; - - ret = pthread_key_create(&tlsstr_key, pa_xfree); - if (ret) { - fprintf(stderr, __FILE__ ": CRITICAL: Unable to allocate TLS key (%d)\n", errno); - exit(-1); - } -} - -#elif HAVE_WINDOWS_H - -static DWORD tlsstr_key = TLS_OUT_OF_INDEXES; -static DWORD monitor_key = TLS_OUT_OF_INDEXES; - -static void inittls(void) { - HANDLE mutex; - char name[64]; - - sprintf(name, "pulse%d", (int)GetCurrentProcessId()); - - mutex = CreateMutex(NULL, FALSE, name); - if (!mutex) { - fprintf(stderr, __FILE__ ": CRITICAL: Unable to create named mutex (%d)\n", (int)GetLastError()); - exit(-1); - } - - WaitForSingleObject(mutex, INFINITE); - - if (tlsstr_key == TLS_OUT_OF_INDEXES) { - tlsstr_key = TlsAlloc(); - monitor_key = TlsAlloc(); - if ((tlsstr_key == TLS_OUT_OF_INDEXES) || (monitor_key == TLS_OUT_OF_INDEXES)) { - fprintf(stderr, __FILE__ ": CRITICAL: Unable to allocate TLS key (%d)\n", (int)GetLastError()); - exit(-1); - } - } - - ReleaseMutex(mutex); - - CloseHandle(mutex); -} - -/* - * This is incredibly brain dead, but this is necessary when dealing with - * the hell that is Win32. - */ -struct monitor_data { - HANDLE thread; - void *data; -}; - -static DWORD WINAPI monitor_thread(LPVOID param) { - struct monitor_data *data; - - data = (struct monitor_data*)param; - assert(data); - - WaitForSingleObject(data->thread, INFINITE); - - CloseHandle(data->thread); - pa_xfree(data->data); - pa_xfree(data); - - return 0; -} - -static void start_monitor(void) { - HANDLE thread; - struct monitor_data *data; - - data = pa_xnew(struct monitor_data, 1); - assert(data); - - DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), - GetCurrentProcess(), &data->thread, 0, FALSE, DUPLICATE_SAME_ACCESS); - - thread = CreateThread(NULL, 0, monitor_thread, data, 0, NULL); - assert(thread); - - TlsSetValue(monitor_key, data); - - CloseHandle(thread); -} - -#else - -/* Unsafe, but we have no choice */ -static char *tlsstr; - -#endif +PA_STATIC_TLS_DECLARE(cstrerror, pa_xfree); const char* pa_cstrerror(int errnum) { - const char *origbuf; - -#ifdef HAVE_STRERROR_R + const char *original = NULL; + char *translated, *t; char errbuf[128]; -#endif - -#ifdef HAVE_PTHREAD - char *tlsstr; - - pthread_once(&cstrerror_once, inittls); - - tlsstr = pthread_getspecific(tlsstr_key); -#elif defined(HAVE_WINDOWS_H) - char *tlsstr; - struct monitor_data *data; - - inittls(); - - tlsstr = TlsGetValue(tlsstr_key); - if (!tlsstr) - start_monitor(); - data = TlsGetValue(monitor_key); -#endif - - if (tlsstr) - pa_xfree(tlsstr); -#ifdef HAVE_STRERROR_R + if ((t = PA_STATIC_TLS_GET(cstrerror))) + pa_xfree(t); -#ifdef __GLIBC__ - origbuf = strerror_r(errnum, errbuf, sizeof(errbuf)); - if (origbuf == NULL) - origbuf = ""; -#else +#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) { - origbuf = errbuf; - errbuf[sizeof(errbuf) - 1] = '\0'; - } else - origbuf = ""; -#endif - + errbuf[sizeof(errbuf) - 1] = 0; + original = errbuf; + } #else /* This might not be thread safe, but we hope for the best */ - origbuf = strerror(errnum); + original = strerror(errnum); #endif - tlsstr = pa_locale_to_utf8(origbuf); - if (!tlsstr) { - fprintf(stderr, "Unable to convert, filtering\n"); - tlsstr = pa_utf8_filter(origbuf); + if (!original) { + pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %i", errnum); + original = errbuf; } -#ifdef HAVE_PTHREAD - pthread_setspecific(tlsstr_key, tlsstr); -#elif defined(HAVE_WINDOWS_H) - TlsSetValue(tlsstr_key, tlsstr); - data->data = tlsstr; -#endif + 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 tlsstr; + return translated; } diff --git a/src/pulsecore/core-error.h b/src/pulsecore/core-error.h index 32da8bf2..b0c306c7 100644 --- a/src/pulsecore/core-error.h +++ b/src/pulsecore/core-error.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c index e3bf3ca2..75fa2ff1 100644 --- a/src/pulsecore/core-scache.c +++ b/src/pulsecore/core-scache.c @@ -1,18 +1,19 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <string.h> #include <stdio.h> @@ -57,15 +57,19 @@ #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 +#define UNLOAD_POLL_TIME 60 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; - assert(c && c->mainloop == m && c->scache_auto_unload_event == e); + + pa_assert(c); + pa_assert(c->mainloop == m); + pa_assert(c->scache_auto_unload_event == e); pa_scache_unload_unused(c); @@ -75,31 +79,37 @@ static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED } static void free_entry(pa_scache_entry *e) { - assert(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); + if (e->proplist) + pa_proplist_free(e->proplist); pa_xfree(e); } static pa_scache_entry* scache_add_item(pa_core *c, const char *name) { pa_scache_entry *e; - assert(c && name); + + 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); - - assert(e->core == c); + pa_proplist_clear(e->proplist); + + pa_assert(e->core == c); pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); } else { - e = pa_xmalloc(sizeof(pa_scache_entry)); + e = pa_xnew(pa_scache_entry, 1); if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, 1)) { pa_xfree(e); @@ -108,11 +118,10 @@ static pa_scache_entry* scache_add_item(pa_core *c, const char *name) { e->name = pa_xstrdup(name); e->core = c; + e->proplist = pa_proplist_new(); - if (!c->scache) { + if (!c->scache) c->scache = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - assert(c->scache); - } pa_idxset_put(c->scache, e, &e->index); @@ -120,22 +129,40 @@ static pa_scache_entry* scache_add_item(pa_core *c, const char *name) { } e->last_used_time = 0; - e->memchunk.memblock = NULL; - e->memchunk.index = e->memchunk.length = 0; + pa_memchunk_reset(&e->memchunk); e->filename = NULL; - e->lazy = 0; + e->lazy = FALSE; 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); + pa_proplist_sets(e->proplist, PA_PROP_MEDIA_ROLE, "event"); + return e; } -int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, uint32_t *idx) { +int pa_scache_add_item( + pa_core *c, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_memchunk *chunk, + pa_proplist *p, + uint32_t *idx) { + pa_scache_entry *e; - assert(c && name); + 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) + pa_channel_map_init_extend(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT); if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX) return -1; @@ -143,9 +170,11 @@ int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, c 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; - pa_channel_map_init_auto(&e->channel_map, ss->channels, PA_CHANNEL_MAP_DEFAULT); e->volume.channels = e->sample_spec.channels; } @@ -157,9 +186,16 @@ int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, c pa_memblock_ref(e->memchunk.memblock); } + if (p) + pa_proplist_update(e->proplist, PA_UPDATE_REPLACE, p); + if (idx) *idx = e->index; + pa_log_debug("Created sample \"%s\" (#%d), %lu bytes with sample spec %s", + name, e->index, (unsigned long) e->memchunk.length, + pa_sample_spec_snprint(st, sizeof(st), &e->sample_spec)); + return 0; } @@ -168,6 +204,7 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3 pa_channel_map map; pa_memchunk chunk; int r; + pa_proplist *p; #ifdef OS_IS_WIN32 char buf[MAX_PATH]; @@ -176,11 +213,18 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3 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); + + p = pa_proplist_new(); + pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename); + r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx); pa_memblock_unref(chunk.memblock); + pa_proplist_free(p); return r; } @@ -195,14 +239,18 @@ int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, filename = buf; #endif - assert(c && name); + pa_assert(c); + pa_assert(name); + pa_assert(filename); if (!(e = scache_add_item(c, name))) return -1; - e->lazy = 1; + e->lazy = TRUE; e->filename = pa_xstrdup(filename); - + + pa_proplist_sets(e->proplist, PA_PROP_MEDIA_FILENAME, filename); + if (!c->scache_auto_unload_event) { struct timeval ntv; pa_gettimeofday(&ntv); @@ -218,26 +266,31 @@ int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, int pa_scache_remove_item(pa_core *c, const char *name) { pa_scache_entry *e; - assert(c && name); + + 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) - assert(0); + pa_assert_se(pa_idxset_remove_by_data(c->scache, e, NULL) == e); + + pa_log_debug("Removed sample \"%s\"", name); free_entry(e); + return 0; } static void free_cb(void *p, PA_GCC_UNUSED void *userdata) { pa_scache_entry *e = p; - assert(e); + pa_assert(e); + free_entry(e); } void pa_scache_free(pa_core *c) { - assert(c); + pa_assert(c); if (c->scache) { pa_idxset_free(c->scache, free_cb, NULL); @@ -248,14 +301,14 @@ void pa_scache_free(pa_core *c) { 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) { +int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { pa_scache_entry *e; - char *t; pa_cvolume r; - - assert(c); - assert(name); - assert(sink); + pa_proplist *merged; + + pa_assert(c); + pa_assert(name); + pa_assert(sink); if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 1))) return -1; @@ -269,31 +322,54 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t if (e->volume.channels > e->sample_spec.channels) e->volume.channels = e->sample_spec.channels; } - + if (!e->memchunk.memblock) return -1; - t = pa_sprintf_malloc("sample:%s", name); + pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->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); + merged = pa_proplist_new(); + + pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name); + + pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist); + + if (p) + pa_proplist_update(merged, PA_UPDATE_REPLACE, p); + + if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, &r, merged, sink_input_idx) < 0) { + pa_proplist_free(merged); return -1; } - pa_xfree(t); + pa_proplist_free(merged); if (e->lazy) time(&e->last_used_time); - + return 0; } -const char * pa_scache_get_name_by_id(pa_core *c, uint32_t id) { +int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_bool_t autoload, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { + pa_sink *sink; + + pa_assert(c); + pa_assert(name); + + if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, autoload))) + return -1; + + return pa_scache_play_item(c, name, sink, volume, p, sink_input_idx); +} + +const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id) { pa_scache_entry *e; - assert(c && id != PA_IDXSET_INVALID); + + pa_assert(c); + pa_assert(id != PA_IDXSET_INVALID); if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id))) return NULL; @@ -303,7 +379,9 @@ 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) { pa_scache_entry *e; - assert(c && name); + + pa_assert(c); + pa_assert(name); if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0))) return PA_IDXSET_INVALID; @@ -311,14 +389,16 @@ uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) { return e->index; } -uint32_t pa_scache_total_size(pa_core *c) { +size_t pa_scache_total_size(pa_core *c) { pa_scache_entry *e; - uint32_t idx, sum = 0; - assert(c); + uint32_t idx; + size_t 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; @@ -330,11 +410,12 @@ void pa_scache_unload_unused(pa_core *c) { pa_scache_entry *e; time_t now; uint32_t idx; - assert(c); + + 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)) { @@ -344,10 +425,9 @@ void pa_scache_unload_unused(pa_core *c) { 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_memchunk_reset(&e->memchunk); pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); } @@ -357,8 +437,11 @@ 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; @@ -372,7 +455,9 @@ static void add_file(pa_core *c, const char *pathname) { int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) { DIR *dir; - assert(c && pathname); + + pa_core_assert_ref(c); + pa_assert(pathname); /* First try to open this as directory */ if (!(dir = opendir(pathname))) { @@ -388,7 +473,7 @@ int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) { for (i = 0; i < p.gl_pathc; i++) add_file(c, p.gl_pathv[i]); - + globfree(&p); #else return -1; @@ -402,11 +487,12 @@ int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) { if (e->d_name[0] == '.') continue; - snprintf(p, sizeof(p), "%s/%s", pathname, e->d_name); + pa_snprintf(p, sizeof(p), "%s/%s", pathname, e->d_name); add_file(c, p); } + + closedir(dir); } - closedir(dir); return 0; } diff --git a/src/pulsecore/core-scache.h b/src/pulsecore/core-scache.h index d01aae9b..80e0fd04 100644 --- a/src/pulsecore/core-scache.h +++ b/src/pulsecore/core-scache.h @@ -1,21 +1,22 @@ #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 @@ -26,38 +27,42 @@ #include <pulsecore/memchunk.h> #include <pulsecore/sink.h> -#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*2) +#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*16) typedef struct pa_scache_entry { - pa_core *core; uint32_t index; + pa_core *core; + char *name; - + pa_cvolume volume; pa_sample_spec sample_spec; pa_channel_map channel_map; pa_memchunk memchunk; char *filename; - - int lazy; + + pa_bool_t lazy; time_t last_used_time; + + pa_proplist *proplist; } pa_scache_entry; -int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, uint32_t *idx); +int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, pa_proplist *p, uint32_t *idx); int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx); int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx); int pa_scache_add_directory_lazy(pa_core *c, const char *pathname); int pa_scache_remove_item(pa_core *c, const char *name); -int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume); +int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx); +int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_bool_t autoload, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx); 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); +size_t pa_scache_total_size(pa_core *c); void pa_scache_unload_unused(pa_core *c); diff --git a/src/pulsecore/core-subscribe.c b/src/pulsecore/core-subscribe.c index 4df1d511..6107002b 100644 --- a/src/pulsecore/core-subscribe.c +++ b/src/pulsecore/core-subscribe.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,12 +24,12 @@ #endif #include <stdio.h> -#include <assert.h> #include <pulse/xmalloc.h> #include <pulsecore/queue.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "core-subscribe.h" @@ -65,10 +65,10 @@ 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; - - assert(c); - assert(m); - assert(callback); + + pa_assert(c); + pa_assert(m); + pa_assert(callback); s = pa_xnew(pa_subscription, 1); s->core = c; @@ -83,36 +83,36 @@ pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_su /* Free a subscription object, effectively marking it for deletion */ void pa_subscription_free(pa_subscription*s) { - assert(s); - assert(!s->dead); - + pa_assert(s); + pa_assert(!s->dead); + s->dead = 1; sched_event(s->core); } static void free_subscription(pa_subscription *s) { - assert(s); - assert(s->core); + 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) { - assert(s); - assert(s->core); + 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) { - assert(c); - + pa_assert(c); + while (c->subscriptions) free_subscription(c->subscriptions); @@ -157,10 +157,10 @@ static void dump_event(const char * prefix, pa_subscription_event*e) { static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) { pa_core *c = userdata; pa_subscription *s; - - assert(c->mainloop == m); - assert(c); - assert(c->subscription_defer_event == de); + + pa_assert(c->mainloop == m); + pa_assert(c); + pa_assert(c->subscription_defer_event == de); c->mainloop->defer_enable(c->subscription_defer_event, 0); @@ -170,7 +170,7 @@ static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) { 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); } @@ -182,7 +182,7 @@ static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) { } /* Remove dead subscriptions */ - + s = c->subscriptions; while (s) { pa_subscription *n = s->next; @@ -194,38 +194,38 @@ static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) { /* Schedule an mainloop event so that a pending subscription event is dispatched */ static void sched_event(pa_core *c) { - assert(c); + pa_assert(c); if (!c->subscription_defer_event) { c->subscription_defer_event = c->mainloop->defer_new(c->mainloop, defer_cb, c); - assert(c->subscription_defer_event); + 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 index) { +void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx) { pa_subscription_event *e; - assert(c); + 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 != index) + if (i->index != idx) continue; if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { @@ -251,9 +251,9 @@ void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t i e = pa_xnew(pa_subscription_event, 1); e->core = c; e->type = t; - e->index = index; + e->index = idx; - PA_LLIST_INSERT_AFTER(pa_subscription_event, c->subscription_event_queue, c->subscription_event_last, e); + PA_LLIST_INSERT_AFTER(pa_subscription_event, c->subscription_event_queue, c->subscription_event_last, e); c->subscription_event_last = e; #ifdef DEBUG diff --git a/src/pulsecore/core-subscribe.h b/src/pulsecore/core-subscribe.h index 6e3b646e..2f9730d9 100644 --- a/src/pulsecore/core-subscribe.h +++ b/src/pulsecore/core-subscribe.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 5f72b342..d259fb16 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -1,18 +1,20 @@ -/* $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 @@ -27,7 +29,6 @@ #include <stdlib.h> #include <signal.h> #include <errno.h> -#include <assert.h> #include <string.h> #include <stdio.h> #include <fcntl.h> @@ -38,6 +39,11 @@ #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> +#include <dirent.h> + +#ifdef HAVE_STRTOF_L +#include <locale.h> +#endif #ifdef HAVE_SCHED_H #include <sched.h> @@ -47,6 +53,14 @@ #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 @@ -67,14 +81,19 @@ #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" @@ -83,14 +102,6 @@ #define MSG_NOSIGNAL 0 #endif -#ifndef OS_IS_WIN32 -#define PA_USER_RUNTIME_PATH_PREFIX "/tmp/pulse-" -#define PATH_SEP '/' -#else -#define PA_USER_RUNTIME_PATH_PREFIX "%TEMP%\\pulse-" -#define PATH_SEP '\\' -#endif - #ifdef OS_IS_WIN32 #define PULSE_ROOTENV "PULSE_ROOT" @@ -103,7 +114,7 @@ int pa_set_root(HANDLE handle) { if (!GetModuleFileName(handle, library_path + sizeof(PULSE_ROOTENV), MAX_PATH)) return 0; - sep = strrchr(library_path, '\\'); + sep = strrchr(library_path, PA_PATH_SEP_CHAR); if (sep) *sep = '\0'; @@ -116,31 +127,50 @@ int pa_set_root(HANDLE handle) { #endif /** Make a file descriptor nonblock. Doesn't do any error checking */ -void pa_make_nonblock_fd(int fd) { +void pa_make_fd_nonblock(int fd) { + #ifdef O_NONBLOCK int v; - assert(fd >= 0); + 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); - if ((v = fcntl(fd, F_GETFL)) >= 0) - if (!(v & O_NONBLOCK)) - fcntl(fd, F_SETFL, v|O_NONBLOCK); #elif defined(OS_IS_WIN32) u_long arg = 1; if (ioctlsocket(fd, FIONBIO, &arg) < 0) { - if (WSAGetLastError() == WSAENOTSOCK) - pa_log_warn("WARNING: Only sockets can be made non-blocking!"); + pa_assert_se(WSAGetLastError() == WSAENOTSOCK); + pa_log_warn("Only sockets can be made non-blocking!"); } #else - pa_log_warn("WARNING: Non-blocking I/O not supported.!"); + 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; - - assert(dir); + + pa_assert(dir); #ifdef OS_IS_WIN32 r = mkdir(dir); @@ -152,7 +182,7 @@ int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) { umask(u); } #endif - + if (r < 0 && errno != EEXIST) return -1; @@ -161,20 +191,20 @@ int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) { uid = getuid(); if (gid == (gid_t)-1) gid = getgid(); - chown(dir, uid, gid); + (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) || @@ -184,11 +214,11 @@ int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) { goto fail; } #else - pa_log_warn("secure directory creation not supported on Win32."); + pa_log_warn("Secure directory creation not supported on Win32."); #endif - + return 0; - + fail: rmdir(dir); return -1; @@ -214,12 +244,12 @@ int pa_make_secure_parent_dir(const char *fn, mode_t m, uid_t uid, gid_t gid) { 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; @@ -237,7 +267,7 @@ ssize_t pa_read(int fd, void *buf, size_t count, int *type) { if (!type || *type == 0) { ssize_t r; - + if ((r = recv(fd, buf, count, 0)) >= 0) return r; @@ -251,7 +281,7 @@ ssize_t pa_read(int fd, void *buf, size_t count, int *type) { } #endif - + return read(fd, buf, count); } @@ -263,7 +293,7 @@ ssize_t pa_write(int fd, const void *buf, size_t count, int *type) { if ((r = send(fd, buf, count, MSG_NOSIGNAL)) >= 0) return r; - + #ifdef OS_IS_WIN32 if (WSAGetLastError() != WSAENOTSOCK) { errno = WSAGetLastError(); @@ -286,10 +316,10 @@ 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 ret = 0; int _type; - - assert(fd >= 0); - assert(data); - assert(size); + + pa_assert(fd >= 0); + pa_assert(data); + pa_assert(size); if (!type) { _type = 0; @@ -304,7 +334,7 @@ ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) { if (r == 0) break; - + ret += r; data = (uint8_t*) data + r; size -= r; @@ -318,9 +348,9 @@ ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) { ssize_t ret = 0; int _type; - assert(fd >= 0); - assert(data); - assert(size); + pa_assert(fd >= 0); + pa_assert(data); + pa_assert(size); if (!type) { _type = 0; @@ -335,7 +365,7 @@ ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) { if (r == 0) break; - + ret += r; data = (const uint8_t*) data + r; size -= r; @@ -344,6 +374,25 @@ ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) { 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) { @@ -354,8 +403,8 @@ void pa_check_signal_is_blocked(int sig) { /* 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 + +#ifdef HAVE_PTHREAD if (pthread_sigmask(SIG_SETMASK, NULL, &set) < 0) { #endif if (sigprocmask(SIG_SETMASK, NULL, &set) < 0) { @@ -370,18 +419,18 @@ void pa_check_signal_is_blocked(int 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("WARNING: %s is not trapped. This might cause malfunction!", pa_strsignal(sig)); + + pa_log_warn("%s is not trapped. This might cause malfunction!", pa_sig2str(sig)); #else /* HAVE_SIGACTION */ - pa_log("WARNING: %s might not be trapped. This might cause malfunction!", pa_strsignal(sig)); + pa_log_warn("%s might not be trapped. This might cause malfunction!", pa_sig2str(sig)); #endif } @@ -390,9 +439,9 @@ void pa_check_signal_is_blocked(int sig) { char *pa_sprintf_malloc(const char *format, ...) { int size = 100; char *c = NULL; - - assert(format); - + + pa_assert(format); + for(;;) { int r; va_list ap; @@ -402,12 +451,14 @@ char *pa_sprintf_malloc(const char *format, ...) { 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; + size = r+1; else /* glibc 2.0 */ size *= 2; } @@ -418,25 +469,26 @@ char *pa_sprintf_malloc(const char *format, ...) { char *pa_vsprintf_malloc(const char *format, va_list ap) { int size = 100; char *c = NULL; - - assert(format); - + + pa_assert(format); + for(;;) { int r; va_list aq; - va_copy(aq, ap); - c = pa_xrealloc(c, size); - r = vsnprintf(c, size, format, aq); + 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; + size = r+1; else /* glibc 2.0 */ size *= 2; } @@ -444,96 +496,193 @@ char *pa_vsprintf_malloc(const char *format, va_list ap) { /* Similar to OpenBSD's strlcpy() function */ char *pa_strlcpy(char *b, const char *s, size_t l) { - assert(b && s && l > 0); + pa_assert(b); + pa_assert(s); + pa_assert(l > 0); strncpy(b, s, l); b[l-1] = 0; return b; } -#define NICE_LEVEL (-15) +/* 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) { -/* Raise the priority of the current process as much as possible and -sensible: set the nice level to -15 and enable realtime scheduling if -supported.*/ -void pa_raise_priority(void) { - -#ifdef HAVE_SYS_RESOURCE_H - if (setpriority(PRIO_PROCESS, 0, NICE_LEVEL) < 0) - pa_log_warn("setpriority(): %s", pa_cstrerror(errno)); - else - pa_log_info("Successfully gained nice level %i.", NICE_LEVEL); -#endif - #ifdef _POSIX_PRIORITY_SCHEDULING - { - struct sched_param sp; + struct sched_param sp; + int r, policy; - if (sched_getparam(0, &sp) < 0) { - pa_log("sched_getparam(): %s", pa_cstrerror(errno)); - return; - } - - sp.sched_priority = 1; - if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0) { - pa_log_warn("sched_setscheduler(): %s", pa_cstrerror(errno)); - return; + 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_info("Successfully enabled SCHED_FIFO scheduling."); + pa_log_warn("pthread_setschedparam(): %s", pa_cstrerror(r)); + return -1; } -#endif -#ifdef OS_IS_WIN32 - if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) - pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError()); - else - pa_log_info("Successfully gained high priority class."); + pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread, with priority %i.", sp.sched_priority); + return 0; +#else + return -1; #endif } -/* Reset the priority to normal, inverting the changes made by pa_raise_priority() */ -void pa_reset_priority(void) { -#ifdef OS_IS_WIN32 - SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); +/* This is merely used for giving the user a hint. This is not correct + * for anything security related */ +pa_bool_t pa_can_realtime(void) { + + if (geteuid() == 0) + return TRUE; + +#if defined(HAVE_SYS_RESOURCE_H) && defined(RLIMIT_RTPRIO) + { + struct rlimit rl; + + if (getrlimit(RLIMIT_RTPRIO, &rl) >= 0) + if (rl.rlim_cur > 0 || rl.rlim_cur == RLIM_INFINITY) + return TRUE; + } #endif -#ifdef _POSIX_PRIORITY_SCHEDULING +#if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_SYS_NICE) { - struct sched_param sp; - sched_getparam(0, &sp); - sp.sched_priority = 0; - sched_setscheduler(0, SCHED_OTHER, &sp); + cap_t cap; + + if ((cap = cap_get_proc())) { + cap_flag_value_t flag = CAP_CLEAR; + + if (cap_get_flag(cap, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0) + if (flag == CAP_SET) { + cap_free(cap); + return TRUE; + } + + cap_free(cap); + } } #endif -#ifdef HAVE_SYS_RESOURCE_H - setpriority(PRIO_PROCESS, 0, 0); + return FALSE; +} + +/* This is merely used for giving the user a hint. This is not correct + * for anything security related */ +pa_bool_t pa_can_high_priority(void) { + + if (geteuid() == 0) + return TRUE; + +#if defined(HAVE_SYS_RESOURCE_H) && defined(RLIMIT_RTPRIO) + { + struct rlimit rl; + + if (getrlimit(RLIMIT_NICE, &rl) >= 0) + if (rl.rlim_cur >= 21 || rl.rlim_cur == RLIM_INFINITY) + return TRUE; + } #endif + +#if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_SYS_NICE) + { + cap_t cap; + + if ((cap = cap_get_proc())) { + cap_flag_value_t flag = CAP_CLEAR; + + if (cap_get_flag(cap, CAP_SYS_NICE, CAP_EFFECTIVE, &flag) >= 0) + if (flag == CAP_SET) { + cap_free(cap); + return TRUE; + } + + cap_free(cap); + } + } +#endif + + return FALSE; } -/* Set the FD_CLOEXEC flag for a fd */ -int pa_fd_set_cloexec(int fd, int b) { +/* 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 FD_CLOEXEC - int v; - assert(fd >= 0); +#ifdef HAVE_SYS_RESOURCE_H + if (setpriority(PRIO_PROCESS, 0, nice_level) < 0) { + int n; - if ((v = fcntl(fd, F_GETFD, 0)) < 0) - return -1; - - v = (v & ~FD_CLOEXEC) | (b ? FD_CLOEXEC : 0); - - if (fcntl(fd, F_SETFD, v) < 0) + 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; -#endif + } + + 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) { - + pa_assert(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")) @@ -552,7 +701,7 @@ char *pa_split(const char *c, const char *delimiter, const char**state) { if (!*current) return NULL; - + l = strcspn(current, delimiter); *state = current+l; @@ -581,31 +730,134 @@ char *pa_split_spaces(const char *c, const char **state) { return pa_xstrndup(current, l); } -/* Return the name of an UNIX signal. Similar to GNU's strsignal() */ -const char *pa_strsignal(int sig) { +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) { - case SIGINT: return "SIGINT"; - case SIGTERM: return "SIGTERM"; +#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"; + case SIGUSR1: return "SIGUSR1"; #endif + case SIGSEGV: return "SIGSEGV"; #ifdef SIGUSR2 - case SIGUSR2: return "SIGUSR2"; -#endif -#ifdef SIGXCPU - case SIGXCPU: return "SIGXCPU"; + case SIGUSR2: return "SIGUSR2"; #endif #ifdef SIGPIPE - case SIGPIPE: return "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"; + case SIGCHLD: return "SIGCHLD"; #endif -#ifdef SIGHUP - case SIGHUP: return "SIGHUP"; +#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 - default: return "UNKNOWN SIGNAL"; +#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 @@ -632,7 +884,7 @@ static int is_group(gid_t gid, const char *name) { } r = strcmp(name, result->gr_name) == 0; - + finish: pa_xfree(data); #else @@ -647,7 +899,7 @@ finish: finish: #endif - + return r; } @@ -657,10 +909,10 @@ int pa_own_uid_in_group(const char *name, gid_t *gid) { int n = sysconf(_SC_NGROUPS_MAX); int r = -1, i; - assert(n > 0); - + 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; @@ -681,7 +933,7 @@ int pa_own_uid_in_group(const char *name, gid_t *gid) { } r = 0; - + finish: pa_xfree(gids); @@ -695,20 +947,20 @@ int pa_uid_in_group(uid_t uid, const char *name) { 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; @@ -763,7 +1015,7 @@ int pa_check_in_group(gid_t g) { 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) { @@ -787,7 +1039,7 @@ int pa_lock_fd(int fd, int b) { 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; @@ -802,9 +1054,8 @@ int pa_lock_fd(int fd, int b) { if (fcntl(fd, F_SETLKW, &flock) >= 0) return 0; } - - pa_log("%slock: %s", !b? "un" : "", - pa_cstrerror(errno)); + + pa_log("%slock: %s", !b? "un" : "", pa_cstrerror(errno)); #endif #ifdef OS_IS_WIN32 @@ -823,7 +1074,7 @@ int pa_lock_fd(int fd, int b) { /* Remove trailing newlines from a string */ char* pa_strip_nl(char *s) { - assert(s); + pa_assert(s); s[strcspn(s, "\r\n")] = 0; return s; @@ -832,50 +1083,58 @@ char* pa_strip_nl(char *s) { /* Create a temporary lock file and lock it. */ int pa_lock_lockfile(const char *fn) { int fd = -1; - assert(fn); + pa_assert(fn); for (;;) { struct stat st; - - if ((fd = open(fn, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR)) < 0) { - pa_log("failed to create lock file '%s': %s", fn, - pa_cstrerror(errno)); + + 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("failed to lock file '%s'.", fn); + pa_log_warn("Failed to lock file '%s'.", fn); goto fail; } - + if (fstat(fd, &st) < 0) { - pa_log("failed to fstat() file '%s'.", fn); + 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 */ + /* 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("failed to unlock file '%s'.", fn); + pa_log_warn("Failed to unlock file '%s'.", fn); goto fail; } - - if (close(fd) < 0) { - pa_log("failed to close file '%s'.", fn); + + 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) - close(fd); + pa_close(fd); return -1; } @@ -883,38 +1142,80 @@ fail: /* Unlock a temporary lcok file */ int pa_unlock_lockfile(const char *fn, int fd) { int r = 0; - assert(fn && fd >= 0); + pa_assert(fd >= 0); - if (unlink(fn) < 0) { - pa_log_warn("WARNING: unable to remove lock file '%s': %s", - fn, pa_cstrerror(errno)); - r = -1; + if (fn) { + if (unlink(fn) < 0) { + pa_log_warn("Unable to remove lock file '%s': %s", fn, pa_cstrerror(errno)); + r = -1; + } } - + if (pa_lock_fd(fd, 0) < 0) { - pa_log_warn("WARNING: failed to unlock file '%s'.", fn); + pa_log_warn("Failed to unlock file '%s'.", fn); r = -1; } - if (close(fd) < 0) { - pa_log_warn("WARNING: failed to close lock file '%s': %s", - fn, pa_cstrerror(errno)); + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close '%s': %s", fn, pa_cstrerror(errno)); r = -1; } return r; } +static char *get_dir(mode_t m, const char *env_name) { + const char *e; + char *d; + + if ((e = getenv(env_name))) + d = pa_xstrdup(e); + else { + char h[PATH_MAX]; + struct stat st; + + if (!pa_get_home_dir(h, sizeof(h))) { + pa_log_error("Failed to get home directory."); + return NULL; + } + + if (stat(h, &st) < 0) { + pa_log_error("Failed to stat home directory %s: %s", h, pa_cstrerror(errno)); + return NULL; + } + + if (st.st_uid != getuid()) { + pa_log_error("Home directory %s not ours.", d); + return NULL; + } + + d = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h); + } + + if (pa_make_secure_dir(d, m, (pid_t) -1, (pid_t) -1) < 0) { + pa_log_error("Failed to create secure directory: %s", pa_cstrerror(errno)); + return NULL; + } + + return d; +} + +char *pa_get_runtime_dir(void) { + return get_dir(pa_in_system_mode() ? 0755 : 0700, "PULSE_RUNTIME_PATH"); +} + +char *pa_get_state_dir(void) { + return get_dir(0700, "PULSE_STATE_PATH"); +} + /* 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) { +FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result) { const char *fn; - char h[PATH_MAX]; - #ifdef OS_IS_WIN32 char buf[PATH_MAX]; @@ -923,71 +1224,162 @@ FILE *pa_open_config_file(const char *global, const char *local, const char *env #endif if (env && (fn = getenv(env))) { + FILE *f; + #ifdef OS_IS_WIN32 if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX)) return NULL; fn = buf; #endif - if (result) - *result = pa_xstrdup(fn); + if ((f = fopen(fn, "r"))) { + if (result) + *result = pa_xstrdup(fn); + + return f; + } - return fopen(fn, mode); + pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno)); + return NULL; } if (local) { const char *e; - char *lfn = NULL; + char *lfn; + char h[PATH_MAX]; + FILE *f; if ((e = getenv("PULSE_CONFIG_PATH"))) - fn = lfn = pa_sprintf_malloc("%s/%s", e, local); + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local); else if (pa_get_home_dir(h, sizeof(h))) - fn = lfn = pa_sprintf_malloc("%s/.pulse/%s", h, local); + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local); - if (lfn) { - FILE *f; - #ifdef OS_IS_WIN32 - if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) - return NULL; - fn = buf; -#endif - - if ((f = fopen(fn, mode)) || errno != ENOENT) { - if (result) - *result = pa_xstrdup(fn); - pa_xfree(lfn); - return f; - } - + if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) { pa_xfree(lfn); + return NULL; } + fn = buf; +#endif + + if ((f = fopen(fn, "r"))) { + if (result) + *result = pa_xstrdup(fn); + + pa_xfree(lfn); + return f; + } + + if (errno != ENOENT) { + pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno)); + pa_xfree(lfn); + return NULL; + } + + pa_xfree(lfn); } - if (!global) { - if (result) - *result = NULL; + if (global) { + FILE *f; + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) + return NULL; + global = buf; +#endif + + if ((f = fopen(global, "r"))) { + + if (result) + *result = pa_xstrdup(global); + + return f; + } + } else errno = ENOENT; + + return NULL; +} + +char *pa_find_config_file(const char *global, const char *local, const char *env) { + const char *fn; +#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 (access(fn, R_OK) == 0) + return pa_xstrdup(fn); + + pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno)); return NULL; } + if (local) { + const char *e; + char *lfn; + char h[PATH_MAX]; + + if ((e = getenv("PULSE_CONFIG_PATH"))) + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local); + else if (pa_get_home_dir(h, sizeof(h))) + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local); + #ifdef OS_IS_WIN32 - if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) - return NULL; - global = buf; + if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) { + pa_xfree(lfn); + return NULL; + } + fn = buf; #endif - if (result) - *result = pa_xstrdup(global); - - return fopen(global, mode); + if (access(fn, R_OK) == 0) { + char *r = pa_xstrdup(fn); + pa_xfree(lfn); + return r; + } + + if (errno != ENOENT) { + pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno)); + pa_xfree(lfn); + return NULL; + } + + pa_xfree(lfn); + } + + if (global) { +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) + return NULL; + global = buf; +#endif + + if (access(fn, R_OK) == 0) + return pa_xstrdup(global); + } else + errno = ENOENT; + + return NULL; } - + /* Format the specified data as a hexademical string */ char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength) { size_t i = 0, j = 0; const char hex[] = "0123456789abcdef"; - assert(d && s && slength > 0); + + pa_assert(d); + pa_assert(s); + pa_assert(slength > 0); while (i < dlength && j+3 <= slength) { s[j++] = hex[*d >> 4]; @@ -1018,14 +1410,16 @@ static int hexc(char c) { /* 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; - assert(p && d); + + 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) @@ -1042,84 +1436,107 @@ size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength) { } /* Returns nonzero when *s starts with *pfx */ -int pa_startswith(const char *s, const char *pfx) { +pa_bool_t pa_startswith(const char *s, const char *pfx) { size_t l; - - assert(s); - assert(pfx); - + + 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) { +pa_bool_t pa_endswith(const char *s, const char *sfx) { size_t l1, l2; - - assert(s); - assert(sfx); - + + 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; +pa_bool_t pa_is_path_absolute(const char *fn) { + pa_assert(fn); #ifndef OS_IS_WIN32 - if (fn && *fn == '/') + return *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) - snprintf(s, l, "%s%c%s", e, PATH_SEP, fn); - else - snprintf(s, l, "%s", e); - - } else { - char u[256]; - - if (fn) - snprintf(s, l, "%s%s%c%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u)), PATH_SEP, fn); - else - 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); - } + return strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\'; #endif +} - return s; +char *pa_make_path_absolute(const char *p) { + char *r; + char *cwd; + + pa_assert(p); + + if (pa_is_path_absolute(p)) + return pa_xstrdup(p); + + if (!(cwd = pa_getcwd())) + return pa_xstrdup(p); + + r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", cwd, p); + pa_xfree(cwd); + return r; +} + +/* if fn is null return the PulseAudio run time path in s (~/.pulse) + * if fn is non-null and starts with / return fn + * otherwise append fn to the run time path and return it */ +static char *get_path(const char *fn, pa_bool_t rt) { + char *rtp; + + if (pa_is_path_absolute(fn)) + return pa_xstrdup(fn); + + rtp = rt ? pa_get_runtime_dir() : pa_get_state_dir(); + + if (!rtp) + return NULL; + + if (fn) { + char *r; + r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", rtp, fn); + pa_xfree(rtp); + return r; + } else + return rtp; +} + +char *pa_runtime_path(const char *fn) { + return get_path(fn, 1); +} + +char *pa_state_path(const char *fn) { + return get_path(fn, 0); } /* 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; - assert(s && ret_i); + pa_assert(s); + pa_assert(ret_i); + + errno = 0; l = strtol(s, &x, 0); - if (!x || *x) + if (!x || *x || errno != 0) + return -1; + + if ((int32_t) l != l) return -1; *ret_i = (int32_t) l; - + return 0; } @@ -1127,14 +1544,502 @@ int pa_atoi(const char *s, int32_t *ret_i) { int pa_atou(const char *s, uint32_t *ret_u) { char *x = NULL; unsigned long l; - assert(s && ret_u); + pa_assert(s); + pa_assert(ret_u); + + errno = 0; l = strtoul(s, &x, 0); - if (!x || *x) + 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_atod(const char *s, double *ret_d) { + char *x = NULL; + double f; + int r = 0; + + pa_assert(s); + pa_assert(ret_d); + + /* 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 = strtod_l(s, &x, c_locale); + } else +#endif + { + errno = 0; + f = strtod(s, &x); + } + + if (!x || *x || errno != 0) + r = -1; + else + *ret_d = 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 = pa_vsnprintf(str, size, format, ap); + va_end(ap); + + return ret; +} + +/* Same as vsnprintf, but guarantees NUL-termination on every platform */ +int pa_vsnprintf(char *str, size_t size, const char *format, va_list ap) { + int ret; + + pa_assert(str); + pa_assert(size > 0); + pa_assert(format); + + ret = vsnprintf(str, size, format, ap); + + str[size-1] = 0; + + if (ret < 0) + ret = strlen(str); + + return PA_MIN((int) size-1, 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; + } +} + +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; + } +} + +int pa_close_all(int except_fd, ...) { + va_list ap; + int n = 0, i, r; + int *p; + + va_start(ap, except_fd); + + if (except_fd >= 0) + for (n = 1; va_arg(ap, int) >= 0; n++) + ; + + va_end(ap); + + p = pa_xnew(int, n+1); + + va_start(ap, except_fd); + + i = 0; + if (except_fd >= 0) { + int fd; + p[i++] = except_fd; + + while ((fd = va_arg(ap, int)) >= 0) + p[i++] = fd; + } + p[i] = -1; + + va_end(ap); + + r = pa_close_allv(p); + free(p); + + return r; +} + +int pa_close_allv(const int except_fds[]) { + struct rlimit rl; + int fd; + int saved_errno; + +#ifdef __linux__ + + DIR *d; + + if ((d = opendir("/proc/self/fd"))) { + + struct dirent *de; + + while ((de = readdir(d))) { + pa_bool_t found; + long l; + char *e = NULL; + int i; + + if (de->d_name[0] == '.') + continue; + + errno = 0; + l = strtol(de->d_name, &e, 10); + if (errno != 0 || !e || *e) { + closedir(d); + errno = EINVAL; + return -1; + } + + fd = (int) l; + + if ((long) fd != l) { + closedir(d); + errno = EINVAL; + return -1; + } + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + found = FALSE; + for (i = 0; except_fds[i] >= 0; i++) + if (except_fds[i] == fd) { + found = TRUE; + break; + } + + if (found) + continue; + + if (pa_close(fd) < 0) { + saved_errno = errno; + closedir(d); + errno = saved_errno; + + return -1; + } + } + + closedir(d); + return 0; + } + +#endif + + if (getrlimit(RLIMIT_NOFILE, &rl) < 0) + return -1; + + for (fd = 0; fd < (int) rl.rlim_max; fd++) { + int i; + + if (fd <= 3) + continue; + + for (i = 0; except_fds[i] >= 0; i++) + if (except_fds[i] == fd) + continue; + + if (close(fd) < 0 && errno != EBADF) + return -1; + } + return 0; } + +int pa_unblock_sigs(int except, ...) { + va_list ap; + int n = 0, i, r; + int *p; + + va_start(ap, except); + + if (except >= 1) + for (n = 1; va_arg(ap, int) >= 0; n++) + ; + + va_end(ap); + + p = pa_xnew(int, n+1); + + va_start(ap, except); + + i = 0; + if (except >= 1) { + int sig; + p[i++] = except; + + while ((sig = va_arg(ap, int)) >= 0) + p[i++] = sig; + } + p[i] = -1; + + va_end(ap); + + r = pa_unblock_sigsv(p); + pa_xfree(p); + + return r; +} + +int pa_unblock_sigsv(const int except[]) { + int i; + sigset_t ss; + + if (sigemptyset(&ss) < 0) + return -1; + + for (i = 0; except[i] > 0; i++) + if (sigaddset(&ss, except[i]) < 0) + return -1; + + return sigprocmask(SIG_SETMASK, &ss, NULL); +} + +int pa_reset_sigs(int except, ...) { + va_list ap; + int n = 0, i, r; + int *p; + + va_start(ap, except); + + if (except >= 1) + for (n = 1; va_arg(ap, int) >= 0; n++) + ; + + va_end(ap); + + p = pa_xnew(int, n+1); + + va_start(ap, except); + + i = 0; + if (except >= 1) { + p[i++] = except; + + while ((p[i++] = va_arg(ap, int)) >= 0) + ; + } + p[i] = -1; + + va_end(ap); + + r = pa_reset_sigsv(p); + pa_xfree(p); + + return r; +} + +int pa_reset_sigsv(const int except[]) { + int sig; + + for (sig = 1; sig < _NSIG; sig++) { + pa_bool_t reset = TRUE; + + switch (sig) { + case SIGKILL: + case SIGSTOP: + reset = FALSE; + break; + + default: { + int i; + + for (i = 0; except[i] > 0; i++) { + if (sig == except[i]) { + reset = FALSE; + break; + } + } + } + } + + if (reset) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if ((sigaction(sig, &sa, NULL) < 0)) + if (errno != EINVAL) + return -1; + } + } + + return 0; +} + +void pa_set_env(const char *key, const char *value) { + pa_assert(key); + pa_assert(value); + + putenv(pa_sprintf_malloc("%s=%s", key, value)); +} + +pa_bool_t pa_in_system_mode(void) { + const char *e; + + if (!(e = getenv("PULSE_SYSTEM"))) + return FALSE; + + return !!atoi(e); +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index ba325968..2ed81fc5 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -1,21 +1,22 @@ #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 @@ -27,11 +28,29 @@ #include <stdarg.h> #include <stdio.h> -#include <pulsecore/gccmacro.h> +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#include <pulse/gccmacro.h> +#include <pulsecore/macro.h> struct timeval; -void pa_make_nonblock_fd(int fd); +/* These resource limits are pretty new on Linux, let's define them + * here manually, in case the kernel is newer than the glibc */ +#if !defined(RLIMIT_NICE) && defined(__linux__) +#define RLIMIT_NICE 13 +#endif +#if !defined(RLIMIT_RTPRIO) && defined(__linux__) +#define RLIMIT_RTPRIO 14 +#endif +#if !defined(RLIMIT_RTTIME) && defined(__linux__) +#define RLIMIT_RTTIME 15 +#endif + +void pa_make_fd_nonblock(int fd); +void pa_make_fd_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); @@ -41,6 +60,8 @@ 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); @@ -50,19 +71,33 @@ char *pa_strlcpy(char *b, const char *s, size_t l); char *pa_parent_dir(const char *fn); -void pa_raise_priority(void); +int pa_make_realtime(int rtprio); +int pa_raise_priority(int nice_level); void pa_reset_priority(void); -int pa_fd_set_cloexec(int fd, int b); +pa_bool_t pa_can_realtime(void); +pa_bool_t pa_can_high_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"; +} -int pa_parse_boolean(const char *s); +static inline const char *pa_strnull(const char *x) { + return x ? x : "(null)"; +} + +static inline const char *pa_strempty(const char *x) { + return x ? x : ""; +} 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_strsignal(int sig); +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); @@ -74,17 +109,79 @@ 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); -int pa_endswith(const char *s, const char *sfx); +pa_bool_t pa_startswith(const char *s, const char *pfx) PA_GCC_PURE; +pa_bool_t pa_endswith(const char *s, const char *sfx) PA_GCC_PURE; -char *pa_runtime_path(const char *fn, char *s, size_t l); +FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result); +char* pa_find_config_file(const char *global, const char *local, const char *env); + +char *pa_get_runtime_dir(void); +char *pa_get_state_dir(void); +char *pa_runtime_path(const char *fn); +char *pa_state_path(const char *fn); int pa_atoi(const char *s, int32_t *ret_i); int pa_atou(const char *s, uint32_t *ret_u); +int pa_atod(const char *s, double *ret_d); + +int pa_snprintf(char *str, size_t size, const char *format, ...); +int pa_vsnprintf(char *str, size_t size, const char *format, va_list ap); + +char *pa_truncate_utf8(char *c, size_t l); + +char *pa_getcwd(void); +char *pa_make_path_absolute(const char *p); +pa_bool_t pa_is_path_absolute(const char *p); + +void *pa_will_need(const void *p, size_t l); + +static inline int pa_is_power_of_two(unsigned n) { + return !(n & (n - 1)); +} + +static inline unsigned pa_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; +} + +static inline unsigned pa_ulog2(unsigned n) { + unsigned r = 0; + + while (n) { + r++; + n = n >> 1; + } + + return r; +} + +void pa_close_pipe(int fds[2]); + +char *pa_readlink(const char *p); + +int pa_close_all(int except_fd, ...); +int pa_close_allv(const int except_fds[]); +int pa_unblock_sigs(int except, ...); +int pa_unblock_sigsv(const int except[]); +int pa_reset_sigs(int except, ...); +int pa_reset_sigsv(const int except[]); + +void pa_set_env(const char *key, const char *value); + +pa_bool_t pa_in_system_mode(void); + +#define pa_streq(a,b) (!strcmp((a),(b))) #endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 63ee60ca..b2638b10 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,7 +25,6 @@ #endif #include <stdlib.h> -#include <assert.h> #include <stdio.h> #include <signal.h> @@ -42,12 +42,36 @@ #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))) { @@ -63,7 +87,9 @@ pa_core* pa_core_new(pa_mainloop_api *m, int shared) { } } - c = pa_xnew(pa_core, 1); + 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); @@ -79,11 +105,13 @@ pa_core* pa_core_new(pa_mainloop_api *m, int shared) { c->scache = NULL; c->autoload_idxset = NULL; c->autoload_hashmap = NULL; - c->running_as_daemon = 0; + 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; @@ -95,8 +123,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, int shared) { c->subscription_event_last = NULL; c->mempool = pool; - - c->disallow_module_loading = 0; + pa_silence_cache_init(&c->silence_cache); c->quit_event = NULL; @@ -104,44 +131,48 @@ pa_core* pa_core_new(pa_mainloop_api *m, int shared) { c->module_idle_time = 20; c->scache_idle_time = 20; - c->resample_method = PA_RESAMPLER_SRC_SINC_FASTEST; + c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3; - c->is_system_instance = 0; + c->disallow_module_loading = FALSE; + c->realtime_scheduling = FALSE; + c->realtime_priority = 5; + c->disable_remixing = FALSE; - pa_hook_init(&c->hook_sink_input_new, c); - pa_hook_init(&c->hook_sink_disconnect, c); - pa_hook_init(&c->hook_source_output_new, c); - pa_hook_init(&c->hook_source_disconnect, c); + 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; } -void pa_core_free(pa_core *c) { - assert(c); +static void core_free(pa_object *o) { + pa_core *c = PA_CORE(o); + int j; + pa_assert(c); pa_module_unload_all(c); - assert(!c->modules); + pa_assert(!c->modules); - assert(pa_idxset_isempty(c->clients)); + pa_assert(pa_idxset_isempty(c->clients)); pa_idxset_free(c->clients, NULL, NULL); - - assert(pa_idxset_isempty(c->sinks)); + + pa_assert(pa_idxset_isempty(c->sinks)); pa_idxset_free(c->sinks, NULL, NULL); - assert(pa_idxset_isempty(c->sources)); + pa_assert(pa_idxset_isempty(c->sources)); pa_idxset_free(c->sources, NULL, NULL); - - assert(pa_idxset_isempty(c->source_outputs)); + + pa_assert(pa_idxset_isempty(c->source_outputs)); pa_idxset_free(c->source_outputs, NULL, NULL); - - assert(pa_idxset_isempty(c->sink_inputs)); + + pa_assert(pa_idxset_isempty(c->sink_inputs)); pa_idxset_free(c->sink_inputs, NULL, NULL); pa_scache_free(c); @@ -155,36 +186,39 @@ void pa_core_free(pa_core *c) { pa_xfree(c->default_source_name); pa_xfree(c->default_sink_name); + pa_silence_cache_done(&c->silence_cache); pa_mempool_free(c->mempool); pa_property_cleanup(c); - pa_hook_free(&c->hook_sink_input_new); - pa_hook_free(&c->hook_sink_disconnect); - pa_hook_free(&c->hook_source_output_new); - pa_hook_free(&c->hook_source_disconnect); - - pa_xfree(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; - assert(c->quit_event = e); + pa_assert(c->quit_event == e); m->quit(m, 0); } void pa_core_check_quit(pa_core *c) { - assert(c); + pa_assert(c); + + if (!c->quit_event && + c->exit_idle_time >= 0 && + pa_idxset_size(c->clients) == 0) { - 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 index 3a34d297..d9ed46f6 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -1,21 +1,21 @@ #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 @@ -32,21 +32,62 @@ #include <pulsecore/queue.h> #include <pulsecore/llist.h> #include <pulsecore/hook-list.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/sample-util.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, + PA_CORE_HOOK_SINK_FIXATE, + PA_CORE_HOOK_SINK_PUT, + PA_CORE_HOOK_SINK_UNLINK, + PA_CORE_HOOK_SINK_UNLINK_POST, + PA_CORE_HOOK_SINK_STATE_CHANGED, + PA_CORE_HOOK_SINK_PROPLIST_CHANGED, + PA_CORE_HOOK_SOURCE_NEW, + PA_CORE_HOOK_SOURCE_FIXATE, + PA_CORE_HOOK_SOURCE_PUT, + PA_CORE_HOOK_SOURCE_UNLINK, + PA_CORE_HOOK_SOURCE_UNLINK_POST, + PA_CORE_HOOK_SOURCE_STATE_CHANGED, + PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED, + PA_CORE_HOOK_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_STATE_CHANGED, + PA_CORE_HOOK_SINK_INPUT_PROPLIST_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_STATE_CHANGED, + PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_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 */ @@ -59,6 +100,8 @@ struct pa_core { 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; @@ -68,28 +111,33 @@ struct pa_core { pa_subscription_event *subscription_event_last; pa_mempool *mempool; + pa_silence_cache silence_cache; - int disallow_module_loading, running_as_daemon; 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; - - int is_system_instance; + pa_bool_t realtime_scheduling; + int realtime_priority; + pa_bool_t disable_remixing; /* hooks */ - pa_hook - hook_sink_input_new, - hook_sink_disconnect, - hook_source_output_new, - hook_source_disconnect; + 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); -void pa_core_free(pa_core*c); /* Check whether noone is connected to this core */ void pa_core_check_quit(pa_core *c); diff --git a/src/pulsecore/creds.h b/src/pulsecore/creds.h index d92ce598..c15c469b 100644 --- a/src/pulsecore/creds.h +++ b/src/pulsecore/creds.h @@ -1,21 +1,21 @@ #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 @@ -24,7 +24,9 @@ #include <sys/types.h> -/* config.h must be included before this file */ +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> diff --git a/src/pulsecore/dllmain.c b/src/pulsecore/dllmain.c index b86bf04f..269de604 100644 --- a/src/pulsecore/dllmain.c +++ b/src/pulsecore/dllmain.c @@ -1,8 +1,8 @@ -/* $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, diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c index cd1fcb0f..a1fcd8a3 100644 --- a/src/pulsecore/dynarray.c +++ b/src/pulsecore/dynarray.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -24,10 +24,10 @@ #endif #include <string.h> -#include <assert.h> #include <stdlib.h> #include <pulse/xmalloc.h> +#include <pulsecore/macro.h> #include "dynarray.h" @@ -50,7 +50,7 @@ pa_dynarray* pa_dynarray_new(void) { void pa_dynarray_free(pa_dynarray* a, void (*func)(void *p, void *userdata), void *userdata) { unsigned i; - assert(a); + pa_assert(a); if (func) for (i = 0; i < a->n_entries; i++) @@ -62,7 +62,7 @@ void pa_dynarray_free(pa_dynarray* a, void (*func)(void *p, void *userdata), voi } void pa_dynarray_put(pa_dynarray*a, unsigned i, void *p) { - assert(a); + pa_assert(a); if (i >= a->n_allocated) { unsigned n; @@ -83,21 +83,27 @@ void pa_dynarray_put(pa_dynarray*a, unsigned i, void *p) { } unsigned pa_dynarray_append(pa_dynarray*a, void *p) { - unsigned i = a->n_entries; + 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) { - assert(a); + pa_assert(a); + if (i >= a->n_entries) return NULL; - assert(a->data); + pa_assert(a->data); return a->data[i]; } unsigned pa_dynarray_size(pa_dynarray*a) { - assert(a); + pa_assert(a); + return a->n_entries; } diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h index 4ddb526c..82b42082 100644 --- a/src/pulsecore/dynarray.h +++ b/src/pulsecore/dynarray.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/endianmacros.h b/src/pulsecore/endianmacros.h index 65db3feb..26336918 100644 --- a/src/pulsecore/endianmacros.h +++ b/src/pulsecore/endianmacros.h @@ -1,21 +1,22 @@ #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 @@ -24,54 +25,94 @@ #include <inttypes.h> -#ifdef HAVE_CONFIG_H -#include <config.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 -#define INT16_SWAP(x) ( (int16_t) ( ((uint16_t) x >> 8) | ((uint16_t) x << 8) ) ) -#define UINT16_SWAP(x) ( (uint16_t) ( ((uint16_t) x >> 8) | ((uint16_t) x << 8) ) ) -#define INT32_SWAP(x) ( (int32_t) ( ((uint32_t) x >> 24) | ((uint32_t) x << 24) | (((uint32_t) x & 0xFF00) << 8) | ((((uint32_t) x) >> 8) & 0xFF00) ) ) -#define UINT32_SWAP(x) ( (uint32_t) ( ((uint32_t) x >> 24) | ((uint32_t) x << 24) | (((uint32_t) x & 0xFF00) << 8) | ((((uint32_t) x) >> 8) & 0xFF00) ) ) +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 MAYBE_INT32_SWAP(c,x) ((c) ? INT32_SWAP(x) : x) -#define MAYBE_UINT32_SWAP(c,x) ((c) ? UINT32_SWAP(x) : x) +#define PA_MAYBE_FLOAT32_SWAP(c,x) ((c) ? PA_FLOAT32_SWAP(x) : x) #ifdef WORDS_BIGENDIAN - #define INT16_FROM_LE(x) INT16_SWAP(x) - #define INT16_FROM_BE(x) ((int16_t)(x)) + #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 INT16_TO_LE(x) INT16_SWAP(x) - #define INT16_TO_BE(x) ((int16_t)(x)) + #define PA_UINT16_TO_LE(x) PA_UINT16_SWAP(x) + #define PA_UINT16_TO_BE(x) ((uint16_t)(x)) - #define UINT16_FROM_LE(x) UINT16_SWAP(x) - #define UINT16_FROM_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 INT32_FROM_LE(x) INT32_SWAP(x) - #define 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 UINT32_FROM_LE(x) UINT32_SWAP(x) - #define UINT32_FROM_BE(x) ((uint32_t)(x)) + #define PA_UINT32_FROM_LE(x) PA_UINT32_SWAP(x) + #define PA_UINT32_FROM_BE(x) ((uint32_t)(x)) - #define UINT32_TO_LE(x) UINT32_SWAP(x) - #define UINT32_TO_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 INT16_FROM_LE(x) ((int16_t)(x)) - #define INT16_FROM_BE(x) INT16_SWAP(x) + #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 INT16_TO_LE(x) ((int16_t)(x)) - #define INT16_TO_BE(x) INT16_SWAP(x) + #define PA_INT32_FROM_LE(x) ((int32_t)(x)) + #define PA_INT32_FROM_BE(x) PA_INT32_SWAP(x) - #define UINT16_FROM_LE(x) ((uint16_t)(x)) - #define UINT16_FROM_BE(x) UINT16_SWAP(x) + #define PA_INT32_TO_LE(x) ((int32_t)(x)) + #define PA_INT32_TO_BE(x) PA_INT32_SWAP(x) - #define INT32_FROM_LE(x) ((int32_t)(x)) - #define INT32_FROM_BE(x) INT32_SWAP(x) + #define PA_UINT32_FROM_LE(x) ((uint32_t)(x)) + #define PA_UINT32_FROM_BE(x) PA_UINT32_SWAP(x) - #define UINT32_FROM_LE(x) ((uint32_t)(x)) - #define UINT32_FROM_BE(x) UINT32_SWAP(x) + #define PA_UINT32_TO_LE(x) ((uint32_t)(x)) + #define PA_UINT32_TO_BE(x) PA_UINT32_SWAP(x) - #define UINT32_TO_LE(x) ((uint32_t)(x)) - #define UINT32_TO_BE(x) 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..e2691611 --- /dev/null +++ b/src/pulsecore/envelope.c @@ -0,0 +1,781 @@ +/*** + 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 = PA_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..5296415a --- /dev/null +++ b/src/pulsecore/envelope.h @@ -0,0 +1,53 @@ +#ifndef foopulseenvelopehfoo +#define foopulseenvelopehfoo + +/*** + 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 4U + +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 index 9d44f65c..79322ae4 100644 --- a/src/pulsecore/esound.h +++ b/src/pulsecore/esound.h @@ -1,21 +1,21 @@ #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 @@ -203,7 +203,7 @@ 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 (UINT32_SWAP(ESD_ENDIAN_KEY)) +#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..1531e3db --- /dev/null +++ b/src/pulsecore/fdsem.c @@ -0,0 +1,324 @@ +/*** + 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_fdsem_data *data; +}; + +pa_fdsem *pa_fdsem_new(void) { + pa_fdsem *f; + + f = pa_xmalloc(PA_ALIGN(sizeof(pa_fdsem)) + PA_ALIGN(sizeof(pa_fdsem_data))); + +#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]); + } + + f->data = (pa_fdsem_data*) ((uint8_t*) f + PA_ALIGN(sizeof(pa_fdsem))); + + pa_atomic_store(&f->data->waiting, 0); + pa_atomic_store(&f->data->signalled, 0); + pa_atomic_store(&f->data->in_pipe, 0); + + return f; +} + +pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd) { + pa_fdsem *f = NULL; + + pa_assert(data); + pa_assert(event_fd >= 0); + +#ifdef HAVE_EVENTFD + f = pa_xnew(pa_fdsem, 1); + + f->efd = event_fd; + pa_make_fd_cloexec(f->efd); + f->fds[0] = f->fds[1] = -1; + f->data = data; +#endif + + return f; +} + +pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data, int* event_fd) { + pa_fdsem *f = NULL; + + pa_assert(data); + pa_assert(event_fd); + +#ifdef HAVE_EVENTFD + + f = pa_xnew(pa_fdsem, 1); + + if ((f->efd = eventfd(0)) < 0) { + pa_xfree(f); + return NULL; + } + + pa_make_fd_cloexec(f->efd); + f->fds[0] = f->fds[1] = -1; + f->data = data; + + pa_atomic_store(&f->data->waiting, 0); + pa_atomic_store(&f->data->signalled, 0); + pa_atomic_store(&f->data->in_pipe, 0); + +#endif + + return f; +} + +void pa_fdsem_free(pa_fdsem *f) { + pa_assert(f); + +#ifdef HAVE_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->data->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->data->in_pipe, r) > r); +} + +void pa_fdsem_post(pa_fdsem *f) { + pa_assert(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 0, 1)) { + + if (pa_atomic_load(&f->data->waiting)) { + ssize_t r; + char x = 'x'; + + pa_atomic_inc(&f->data->in_pipe); + + for (;;) { + +#ifdef HAVE_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->data->signalled, 1, 0)) + return; + + pa_atomic_inc(&f->data->waiting); + + while (!pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) { + char x[10]; + ssize_t r; + +#ifdef HAVE_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->data->in_pipe, r); + } + + pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1); +} + +int pa_fdsem_try(pa_fdsem *f) { + pa_assert(f); + + flush(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) + return 1; + + return 0; +} + +int pa_fdsem_get(pa_fdsem *f) { + pa_assert(f); + +#ifdef HAVE_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->data->signalled, 1, 0)) + return -1; + + pa_atomic_inc(&f->data->waiting); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) { + pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1); + return -1; + } + return 0; +} + +int pa_fdsem_after_poll(pa_fdsem *f) { + pa_assert(f); + + pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1); + + flush(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) + return 1; + + return 0; +} diff --git a/src/pulsecore/fdsem.h b/src/pulsecore/fdsem.h new file mode 100644 index 00000000..48a77c49 --- /dev/null +++ b/src/pulsecore/fdsem.h @@ -0,0 +1,55 @@ +#ifndef foopulsefdsemhfoo +#define foopulsefdsemhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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; + +typedef struct pa_fdsem_data { + pa_atomic_t waiting; + pa_atomic_t signalled; + pa_atomic_t in_pipe; +} pa_fdsem_data; + +pa_fdsem *pa_fdsem_new(void); +pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd); +pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data, int* event_fd); +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 index cfeeac22..f166ee33 100644 --- a/src/pulsecore/flist.c +++ b/src/pulsecore/flist.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,12 +23,14 @@ #include <config.h> #endif -#include <assert.h> +#include <pulse/xmalloc.h> #include <pulsecore/atomic.h> #include <pulsecore/log.h> #include <pulsecore/thread.h> -#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "flist.h" @@ -88,21 +90,18 @@ enum { }; struct cell { - pa_atomic_int_t state; + pa_atomic_t state; void *data; }; struct pa_flist { - struct cell *cells; unsigned size; - pa_atomic_int_t length; - pa_atomic_int_t read_idx; - pa_atomic_int_t write_idx; + pa_atomic_t length; + pa_atomic_t read_idx; + pa_atomic_t write_idx; }; -static int is_power_of_two(unsigned size) { - return !(size & (size - 1)); -} +#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; @@ -110,13 +109,12 @@ pa_flist *pa_flist_new(unsigned size) { if (!size) size = FLIST_SIZE; - assert(is_power_of_two(size)); - - l = pa_xnew(pa_flist, 1); + pa_assert(pa_is_power_of_two(size)); + + l = pa_xmalloc0(PA_ALIGN(sizeof(pa_flist)) + (sizeof(struct cell) * size)); l->size = size; - l->cells = pa_xnew0(struct cell, size); - + pa_atomic_store(&l->read_idx, 0); pa_atomic_store(&l->write_idx, 0); pa_atomic_store(&l->length, 0); @@ -129,32 +127,37 @@ static int reduce(pa_flist *l, int value) { } void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb) { - assert(l); + 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(&l->cells[idx].state) == STATE_USED) - free_cb(l->cells[idx].data); + if (pa_atomic_load(&cells[idx].state) == STATE_USED) + free_cb(cells[idx].data); idx = reduce(l, idx + 1); } } - pa_xfree(l->cells); pa_xfree(l); } int pa_flist_push(pa_flist*l, void *p) { int idx, len, n; - - assert(l); - assert(p); + 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; @@ -163,13 +166,13 @@ int pa_flist_push(pa_flist*l, void *p) { for (; n > 0 ; n--) { _Y; - if (pa_atomic_cmpxchg(&l->cells[idx].state, STATE_UNUSED, STATE_BUSY)) { + if (pa_atomic_cmpxchg(&cells[idx].state, STATE_UNUSED, STATE_BUSY)) { _Y; pa_atomic_inc(&l->write_idx); _Y; - l->cells[idx].data = p; + cells[idx].data = p; _Y; - pa_atomic_store(&l->cells[idx].state, STATE_USED); + pa_atomic_store(&cells[idx].state, STATE_USED); _Y; pa_atomic_inc(&l->length); return 0; @@ -181,16 +184,19 @@ int pa_flist_push(pa_flist*l, void *p) { #ifdef PROFILE if (len > N_EXTRA_SCAN) - pa_log("WARNING: Didn't find free cell after %u iterations.", len); + 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; - - assert(l); + struct cell *cells; + + pa_assert(l); + + cells = PA_FLIST_CELLS(l); n = len = pa_atomic_load(&l->length) + N_EXTRA_SCAN; _Y; @@ -199,14 +205,14 @@ void* pa_flist_pop(pa_flist*l) { for (; n > 0 ; n--) { _Y; - if (pa_atomic_cmpxchg(&l->cells[idx].state, STATE_USED, STATE_BUSY)) { + if (pa_atomic_cmpxchg(&cells[idx].state, STATE_USED, STATE_BUSY)) { void *p; _Y; pa_atomic_inc(&l->read_idx); _Y; - p = l->cells[idx].data; + p = cells[idx].data; _Y; - pa_atomic_store(&l->cells[idx].state, STATE_UNUSED); + pa_atomic_store(&cells[idx].state, STATE_UNUSED); _Y; pa_atomic_dec(&l->length); @@ -219,8 +225,8 @@ void* pa_flist_pop(pa_flist*l) { #ifdef PROFILE if (len > N_EXTRA_SCAN) - pa_log("WARNING: Didn't find used cell after %u iterations.", len); + 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 index 57c9598b..c040667d 100644 --- a/src/pulsecore/flist.h +++ b/src/pulsecore/flist.h @@ -1,21 +1,21 @@ #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 @@ -23,6 +23,9 @@ ***/ #include <pulse/def.h> +#include <pulse/gccmacro.h> + +#include <pulsecore/once.h> /* A multiple-reader multipler-write lock-free free list implementation */ @@ -36,4 +39,28 @@ void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb); 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 index 55a82396..aa2d703a 100644 --- a/src/pulsecore/g711.c +++ b/src/pulsecore/g711.c @@ -1,2531 +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
+/* + * 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 index 97cedf81..37ebcf72 100644 --- a/src/pulsecore/g711.h +++ b/src/pulsecore/g711.h @@ -13,7 +13,7 @@ ** implied warranty. */ -/** Copied from sox -- Lennart Poettring*/ +/** Copied from sox -- Lennart Poettering */ #include <inttypes.h> @@ -33,7 +33,7 @@ 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); +unsigned char st_14linear2ulaw(int16_t pcm_val); int16_t st_ulaw2linear16(unsigned char); #endif diff --git a/src/pulsecore/hashmap.c b/src/pulsecore/hashmap.c index 81a160a6..b7f4109b 100644 --- a/src/pulsecore/hashmap.c +++ b/src/pulsecore/hashmap.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,13 +24,14 @@ #endif #include <stdlib.h> -#include <assert.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" @@ -47,28 +48,30 @@ 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) { - assert(h); - assert(e); +static void remove_entry(pa_hashmap *h, struct hashmap_entry *e) { + pa_assert(h); + pa_assert(e); if (e->next) e->next->previous = e->previous; @@ -82,31 +85,33 @@ static void remove(pa_hashmap *h, struct hashmap_entry *e) { if (e->bucket_previous) e->bucket_previous->bucket_next = e->bucket_next; else { - assert(e->hash < h->size); + pa_assert(e->hash < h->size); h->data[e->hash] = e->bucket_next; } - pa_xfree(e); + 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) { - assert(h); + pa_assert(h); while (h->first_entry) { if (free_func) free_func(h->first_entry->value, userdata); - remove(h, h->first_entry); + remove_entry(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; - assert(h); - assert(hash < h->size); + 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) @@ -118,30 +123,32 @@ static struct hashmap_entry *get(pa_hashmap *h, unsigned hash, const void *key) int pa_hashmap_put(pa_hashmap *h, const void *key, void *value) { struct hashmap_entry *e; unsigned hash; - assert(h); + pa_assert(h); hash = h->hash_func(key) % h->size; if ((e = get(h, hash, key))) return -1; - - e = pa_xnew(struct hashmap_entry, 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; } @@ -150,7 +157,7 @@ void* pa_hashmap_get(pa_hashmap *h, const void *key) { unsigned hash; struct hashmap_entry *e; - assert(h); + pa_assert(h); hash = h->hash_func(key) % h->size; @@ -164,8 +171,8 @@ void* pa_hashmap_remove(pa_hashmap *h, const void *key) { struct hashmap_entry *e; unsigned hash; void *data; - - assert(h); + + pa_assert(h); hash = h->hash_func(key) % h->size; @@ -173,7 +180,7 @@ void* pa_hashmap_remove(pa_hashmap *h, const void *key) { return NULL; data = e->value; - remove(h, e); + remove_entry(h, e); return data; } @@ -182,41 +189,53 @@ unsigned pa_hashmap_size(pa_hashmap *h) { } void *pa_hashmap_iterate(pa_hashmap *h, void **state, const void **key) { - assert(h); - assert(state); + struct hashmap_entry *e; + + pa_assert(h); + pa_assert(state); + + if (*state == (void*) -1) + goto at_end; - if (!*state) - *state = h->first_entry; + if ((!*state && !h->first_entry)) + goto at_end; + + e = *state ? *state : h->first_entry; + + if (e->next) + *state = e->next; else - *state = ((struct hashmap_entry*) *state)->next; + *state = (void*) -1; - if (!*state) { - if (key) - *key = NULL; - return NULL; - } + if (key) + *key = e->key; + + return e->value; + +at_end: + *state = (void *) -1; if (key) - *key = ((struct hashmap_entry*) *state)->key; - - return ((struct hashmap_entry*) *state)->value; + *key = NULL; + + return NULL; } void* pa_hashmap_steal_first(pa_hashmap *h) { void *data; - - assert(h); + + pa_assert(h); if (!h->first_entry) return NULL; data = h->first_entry->value; - remove(h, h->first_entry); + remove_entry(h, h->first_entry); return data; } void *pa_hashmap_get_first(pa_hashmap *h) { - assert(h); + pa_assert(h); if (!h->first_entry) return NULL; diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h index b8a358ec..a4505c4c 100644 --- a/src/pulsecore/hashmap.h +++ b/src/pulsecore/hashmap.h @@ -1,21 +1,21 @@ #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 @@ -30,11 +30,13 @@ 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*, void (*free_func)(void *p, void *userdata), void *userdata); +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); @@ -47,9 +49,10 @@ 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. */ + modified during iteration -- except for deleting the current entry + via pa_hashmap_remove(). The key of the entry is returned in *key, + if key is non-NULL. After the last entry in the hashmap NULL is + returned. */ void *pa_hashmap_iterate(pa_hashmap *h, void **state, const void**key); void *pa_hashmap_steal_first(pa_hashmap *h); diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c index 91c2598b..0aac4759 100644 --- a/src/pulsecore/hook-list.c +++ b/src/pulsecore/hook-list.c @@ -1,80 +1,89 @@ -/* $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/hook-list.h> +#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) { - assert(hook); + pa_assert(hook); PA_LLIST_HEAD_INIT(pa_hook_slot, hook->slots); - hook->last = NULL; - hook->n_dead = hook->firing = 0; + hook->n_dead = hook->n_firing = 0; hook->data = data; } static void slot_free(pa_hook *hook, pa_hook_slot *slot) { - assert(hook); - assert(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) { - assert(hook); - assert(!hook->firing); + pa_assert(hook); + pa_assert(hook->n_firing == 0); while (hook->slots) slot_free(hook, hook->slots); - + pa_hook_init(hook, NULL); } -pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_cb_t cb, void *data) { - pa_hook_slot *slot; - - assert(cb); +pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) { + pa_hook_slot *slot, *where, *prev; + + pa_assert(cb); slot = pa_xnew(pa_hook_slot, 1); slot->hook = hook; - slot->dead = 0; + slot->dead = FALSE; slot->callback = cb; slot->data = data; - - PA_LLIST_INSERT_AFTER(pa_hook_slot, hook->slots, hook->last, slot); - hook->last = slot; - + slot->priority = prio; + + prev = NULL; + for (where = hook->slots; where; where = where->next) { + if (prio < where->priority) + break; + prev = where; + } + + PA_LLIST_INSERT_AFTER(pa_hook_slot, hook->slots, prev, slot); + return slot; } void pa_hook_slot_free(pa_hook_slot *slot) { - assert(slot); - assert(!slot->dead); - - if (slot->hook->firing > 0) { - slot->dead = 1; + pa_assert(slot); + pa_assert(!slot->dead); + + if (slot->hook->n_firing > 0) { + slot->dead = TRUE; slot->hook->n_dead++; } else slot_free(slot->hook, slot); @@ -83,30 +92,32 @@ void pa_hook_slot_free(pa_hook_slot *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; - - assert(hook); - hook->firing ++; + pa_assert(hook); + + hook->n_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 --; - + + hook->n_firing --; + pa_assert(hook->n_firing >= 0); + for (slot = hook->slots; hook->n_dead > 0 && slot; slot = next) { next = slot->next; - + if (slot->dead) { slot_free(hook, slot); hook->n_dead--; } } + pa_assert(hook->n_dead == 0); + return result; } - diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h index 67e5d1ae..cf85aca5 100644 --- a/src/pulsecore/hook-list.h +++ b/src/pulsecore/hook-list.h @@ -1,30 +1,31 @@ #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> +#include <pulse/gccmacro.h> + +#include <pulsecore/llist.h> typedef struct pa_hook_slot pa_hook_slot; typedef struct pa_hook pa_hook; @@ -35,14 +36,21 @@ typedef enum pa_hook_result { PA_HOOK_CANCEL = -1 } pa_hook_result_t; +typedef enum pa_hook_priority { + PA_HOOK_EARLY = -100, + PA_HOOK_NORMAL = 0, + PA_HOOK_LATE = 100 +} pa_hook_priority_t; + typedef pa_hook_result_t (*pa_hook_cb_t)( void *hook_data, void *call_data, void *slot_data); struct pa_hook_slot { - int dead; + pa_bool_t dead; pa_hook *hook; + pa_hook_priority_t priority; pa_hook_cb_t callback; void *data; PA_LLIST_FIELDS(pa_hook_slot); @@ -50,8 +58,7 @@ struct pa_hook_slot { struct pa_hook { PA_LLIST_HEAD(pa_hook_slot, slots); - pa_hook_slot *last; - int firing, n_dead; + int n_firing, n_dead; void *data; }; @@ -59,7 +66,7 @@ struct pa_hook { 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); +pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data); void pa_hook_slot_free(pa_hook_slot *slot); pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data); diff --git a/src/pulsecore/idxset.c b/src/pulsecore/idxset.c index ee0137a3..7c9520a4 100644 --- a/src/pulsecore/idxset.c +++ b/src/pulsecore/idxset.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,36 +25,39 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> #include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> #include "idxset.h" -typedef struct idxset_entry { +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; -} idxset_entry; +}; struct pa_idxset { pa_hash_func_t hash_func; pa_compare_func_t compare_func; - + unsigned hash_table_size, n_entries; - idxset_entry **hash_table, **array, *iterate_list_head, *iterate_list_tail; + 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; @@ -79,7 +83,7 @@ pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_fun 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(idxset_entry*, s->hash_table_size); + s->hash_table = pa_xnew0(struct idxset_entry*, s->hash_table_size); s->array = NULL; s->array_size = 0; s->index = 0; @@ -92,15 +96,17 @@ pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_fun } void pa_idxset_free(pa_idxset *s, void (*free_func) (void *p, void *userdata), void *userdata) { - assert(s); + pa_assert(s); while (s->iterate_list_head) { - idxset_entry *e = 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); - pa_xfree(e); + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); } pa_xfree(s->hash_table); @@ -108,10 +114,10 @@ void pa_idxset_free(pa_idxset *s, void (*free_func) (void *p, void *userdata), v pa_xfree(s); } -static idxset_entry* hash_scan(pa_idxset *s, idxset_entry* e, const void *p) { - assert(p); +static struct idxset_entry* hash_scan(pa_idxset *s, struct idxset_entry* e, const void *p) { + pa_assert(p); - assert(s->compare_func); + pa_assert(s->compare_func); for (; e; e = e->hash_next) if (s->compare_func(e->data, p) == 0) return e; @@ -121,8 +127,10 @@ static idxset_entry* hash_scan(pa_idxset *s, idxset_entry* e, const void *p) { static void extend_array(pa_idxset *s, uint32_t idx) { uint32_t i, j, l; - idxset_entry** n; - assert(idx >= s->start_index); + struct idxset_entry** n; + + pa_assert(s); + pa_assert(idx >= s->start_index); if (idx < s->start_index + s->array_size) return; @@ -132,47 +140,50 @@ static void extend_array(pa_idxset *s, uint32_t idx) { break; l = idx - s->start_index - i + 100; - n = pa_xnew0(idxset_entry*, l); - + 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 idxset_entry** array_index(pa_idxset*s, uint32_t idx) { +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; - idxset_entry *e, **a; - - assert(s); - assert(p); + struct idxset_entry *e, **a; + + pa_assert(s); + pa_assert(p); - assert(s->hash_func); + pa_assert(s->hash_func); h = s->hash_func(p) % s->hash_table_size; - assert(s->hash_table); + pa_assert(s->hash_table); if ((e = hash_scan(s, s->hash_table[h], p))) { if (idx) *idx = e->index; - + return -1; } - e = pa_xmalloc(sizeof(idxset_entry)); + 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; @@ -187,24 +198,24 @@ int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) { /* Insert into array */ extend_array(s, e->index); a = array_index(s, e->index); - assert(a && !*a); + 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) { - assert(s->iterate_list_head); + pa_assert(s->iterate_list_head); s->iterate_list_tail->iterate_next = e; } else { - assert(!s->iterate_list_head); + pa_assert(!s->iterate_list_head); s->iterate_list_head = e; } s->iterate_list_tail = e; - + s->n_entries++; - assert(s->n_entries >= 1); - + pa_assert(s->n_entries >= 1); + if (idx) *idx = e->index; @@ -212,9 +223,9 @@ int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) { } void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) { - idxset_entry **a; - assert(s); - + struct idxset_entry **a; + pa_assert(s); + if (!(a = array_index(s, idx))) return NULL; @@ -226,13 +237,15 @@ void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) { void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) { unsigned h; - idxset_entry *e; - assert(s && p); - - assert(s->hash_func); + struct idxset_entry *e; + + pa_assert(s); + pa_assert(p); + + pa_assert(s->hash_func); h = s->hash_func(p) % s->hash_table_size; - assert(s->hash_table); + pa_assert(s->hash_table); if (!(e = hash_scan(s, s->hash_table[h], p))) return NULL; @@ -242,21 +255,23 @@ void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) { return e->data; } -static void remove_entry(pa_idxset *s, idxset_entry *e) { - idxset_entry **a; - assert(s && e); +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); - assert(a && *a && *a == e); + 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 @@ -271,17 +286,18 @@ static void remove_entry(pa_idxset *s, idxset_entry *e) { else s->hash_table[e->hash_value] = e->hash_next; - pa_xfree(e); + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); - assert(s->n_entries >= 1); + pa_assert(s->n_entries >= 1); s->n_entries--; } void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx) { - idxset_entry **a; + struct idxset_entry **a; void *data; - - assert(s); + + pa_assert(s); if (!(a = array_index(s, idx))) return NULL; @@ -291,19 +307,21 @@ void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx) { data = (*a)->data; remove_entry(s, *a); - - return data; + + return data; } void* pa_idxset_remove_by_data(pa_idxset*s, const void *data, uint32_t *idx) { - idxset_entry *e; + struct idxset_entry *e; unsigned h; void *r; - - assert(s->hash_func); + + pa_assert(s); + + pa_assert(s->hash_func); h = s->hash_func(data) % s->hash_table_size; - assert(s->hash_table); + pa_assert(s->hash_table); if (!(e = hash_scan(s, s->hash_table[h], data))) return NULL; @@ -317,8 +335,10 @@ void* pa_idxset_remove_by_data(pa_idxset*s, const void *data, uint32_t *idx) { } void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx) { - idxset_entry **a, *e = NULL; - assert(s && idx); + struct idxset_entry **a, *e = NULL; + + pa_assert(s); + pa_assert(idx); if ((a = array_index(s, *idx)) && *a) e = (*a)->iterate_next; @@ -328,13 +348,13 @@ void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx) { if (!e) return NULL; - + *idx = e->index; return e->data; } void* pa_idxset_first(pa_idxset *s, uint32_t *idx) { - assert(s); + pa_assert(s); if (!s->iterate_list_head) return NULL; @@ -345,13 +365,14 @@ void* pa_idxset_first(pa_idxset *s, uint32_t *idx) { } void *pa_idxset_next(pa_idxset *s, uint32_t *idx) { - idxset_entry **a, *e = NULL; - assert(s); - assert(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; @@ -362,13 +383,15 @@ void *pa_idxset_next(pa_idxset *s, uint32_t *idx) { } int pa_idxset_foreach(pa_idxset*s, int (*func)(void *p, uint32_t idx, int *del, void*userdata), void *userdata) { - idxset_entry *e; - assert(s && func); + struct idxset_entry *e; + + pa_assert(s); + pa_assert(func); e = s->iterate_list_head; while (e) { int del = 0, r; - idxset_entry *n = e->iterate_next; + struct idxset_entry *n = e->iterate_next; r = func(e->data, e->index, &del, userdata); @@ -380,17 +403,19 @@ int pa_idxset_foreach(pa_idxset*s, int (*func)(void *p, uint32_t idx, int *del, e = n; } - + return 0; } unsigned pa_idxset_size(pa_idxset*s) { - assert(s); + pa_assert(s); + return s->n_entries; } int pa_idxset_isempty(pa_idxset *s) { - assert(s); + pa_assert(s); + return s->n_entries == 0; } diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h index 1765e843..0a4e528e 100644 --- a/src/pulsecore/idxset.h +++ b/src/pulsecore/idxset.h @@ -1,21 +1,22 @@ #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 @@ -41,11 +42,6 @@ int pa_idxset_trivial_compare_func(const void *a, const void *b); unsigned pa_idxset_string_hash_func(const void *p); int pa_idxset_string_compare_func(const void *a, const void *b); -#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(u) - typedef unsigned (*pa_hash_func_t)(const void *p); typedef int (*pa_compare_func_t)(const void *a, const void *b); diff --git a/src/pulsecore/inet_ntop.c b/src/pulsecore/inet_ntop.c index 483c3e26..87551232 100644 --- a/src/pulsecore/inet_ntop.c +++ b/src/pulsecore/inet_ntop.c @@ -1,18 +1,18 @@ -/* $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 @@ -25,7 +25,6 @@ #include <stdio.h> #include <errno.h> -#include <assert.h> #ifndef HAVE_INET_NTOP @@ -45,7 +44,7 @@ const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) { switch (af) { case AF_INET: - snprintf(dst, cnt, "%d.%d.%d.%d", + pa_snprintf(dst, cnt, "%d.%d.%d.%d", #ifdef WORDS_BIGENDIAN (int)(in->s_addr >> 24) & 0xff, (int)(in->s_addr >> 16) & 0xff, @@ -59,7 +58,7 @@ const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) { #endif break; case AF_INET6: - snprintf(dst, cnt, "%x:%x:%x:%x:%x:%x:%x:%x", + 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], diff --git a/src/pulsecore/inet_pton.c b/src/pulsecore/inet_pton.c index 7b6bbc31..d191e550 100644 --- a/src/pulsecore/inet_pton.c +++ b/src/pulsecore/inet_pton.c @@ -1,18 +1,18 @@ -/* $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 @@ -25,7 +25,6 @@ #include <stdio.h> #include <errno.h> -#include <assert.h> #ifndef HAVE_INET_PTON diff --git a/src/pulsecore/iochannel.c b/src/pulsecore/iochannel.c index af732c26..b40c9815 100644 --- a/src/pulsecore/iochannel.c +++ b/src/pulsecore/iochannel.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,7 +25,6 @@ #endif #include <stdlib.h> -#include <assert.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> @@ -44,6 +44,7 @@ #include <pulsecore/core-util.h> #include <pulsecore/socket-util.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "iochannel.h" @@ -54,23 +55,23 @@ struct pa_iochannel { pa_iochannel_cb_t callback; void*userdata; - - int readable; - int writable; - int hungup; - - int no_close; + + 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) { - assert(io); + pa_assert(io); if (io->input_event == io->output_event && io->input_event) { pa_io_event_flags_t f = PA_IO_EVENT_NULL; - assert(io->input_event); - + pa_assert(io->input_event); + if (!io->readable) f |= PA_IO_EVENT_INPUT; if (!io->writable) @@ -87,33 +88,33 @@ static void enable_mainloop_sources(pa_iochannel *io) { 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; - int changed = 0; - - assert(m); - assert(e); - assert(fd >= 0); - assert(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 = 1; - changed = 1; + io->hungup = TRUE; + changed = TRUE; } if ((f & PA_IO_EVENT_INPUT) && !io->readable) { - io->readable = 1; - changed = 1; - assert(e == io->input_event); + io->readable = TRUE; + changed = TRUE; + pa_assert(e == io->input_event); } - + if ((f & PA_IO_EVENT_OUTPUT) && !io->writable) { - io->writable = 1; - changed = 1; - assert(e == io->output_event); + 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); } @@ -121,9 +122,9 @@ static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_fla pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd) { pa_iochannel *io; - - assert(m); - assert(ifd >= 0 || ofd >= 0); + + pa_assert(m); + pa_assert(ifd >= 0 || ofd >= 0); io = pa_xnew(pa_iochannel, 1); io->ifd = ifd; @@ -133,26 +134,26 @@ pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd) { io->userdata = NULL; io->callback = NULL; - io->readable = 0; - io->writable = 0; - io->hungup = 0; - io->no_close = 0; + io->readable = FALSE; + io->writable = FALSE; + io->hungup = FALSE; + io->no_close = FALSE; io->input_event = io->output_event = NULL; if (ifd == ofd) { - assert(ifd >= 0); - pa_make_nonblock_fd(io->ifd); + 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_nonblock_fd(io->ifd); + 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_nonblock_fd(io->ofd); + pa_make_fd_nonblock(io->ofd); io->output_event = m->io_new(m, ofd, PA_IO_EVENT_OUTPUT, callback, io); } } @@ -161,54 +162,52 @@ pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd) { } void pa_iochannel_free(pa_iochannel*io) { - assert(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) - - close(io->ifd); + pa_close(io->ifd); if (io->ofd >= 0 && io->ofd != io->ifd) - close(io->ofd); + pa_close(io->ofd); } - + pa_xfree(io); } -int pa_iochannel_is_readable(pa_iochannel*io) { - assert(io); - +pa_bool_t pa_iochannel_is_readable(pa_iochannel*io) { + pa_assert(io); + return io->readable || io->hungup; } -int pa_iochannel_is_writable(pa_iochannel*io) { - assert(io); - +pa_bool_t pa_iochannel_is_writable(pa_iochannel*io) { + pa_assert(io); + return io->writable && !io->hungup; } -int pa_iochannel_is_hungup(pa_iochannel*io) { - assert(io); - +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; - - assert(io); - assert(data); - assert(l); - assert(io->ofd >= 0); - - r = pa_write(io->ofd, data, l, &io->ofd_type); - if (r >= 0) { - io->writable = 0; + + 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); } @@ -217,14 +216,13 @@ 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) { ssize_t r; - - assert(io); - assert(data); - assert(io->ifd >= 0); - - r = pa_read(io->ifd, data, l, &io->ifd_type); - if (r >= 0) { - io->readable = 0; + + 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); } @@ -233,16 +231,16 @@ ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l) { #ifdef HAVE_CREDS -int pa_iochannel_creds_supported(pa_iochannel *io) { +pa_bool_t pa_iochannel_creds_supported(pa_iochannel *io) { struct sockaddr_un sa; socklen_t l; - - assert(io); - assert(io->ifd >= 0); - assert(io->ofd == io->ifd); + + 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; @@ -252,9 +250,9 @@ int pa_iochannel_creds_supported(pa_iochannel *io) { int pa_iochannel_creds_enable(pa_iochannel *io) { int t = 1; - assert(io); - assert(io->ifd >= 0); - + 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; @@ -267,26 +265,27 @@ ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l ssize_t r; struct msghdr mh; struct iovec iov; - uint8_t cmsg_data[CMSG_SPACE(sizeof(struct ucred))]; + union { + struct cmsghdr hdr; + uint8_t data[CMSG_SPACE(sizeof(struct ucred))]; + } cmsg; struct ucred *u; - struct cmsghdr *cmsg; - - assert(io); - assert(data); - assert(l); - assert(io->ofd >= 0); + + 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_data, 0, sizeof(cmsg_data)); - cmsg = (struct cmsghdr*) cmsg_data; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; + 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); + u = (struct ucred*) CMSG_DATA(&cmsg.hdr); u->pid = getpid(); if (ucred) { @@ -296,121 +295,143 @@ ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l 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_data; - mh.msg_controllen = sizeof(cmsg_data); + mh.msg_control = &cmsg; + mh.msg_controllen = sizeof(cmsg); mh.msg_flags = 0; if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) { - io->writable = 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, int *creds_valid) { +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; - uint8_t cmsg_data[CMSG_SPACE(sizeof(struct ucred))]; - - assert(io); - assert(data); - assert(l); - assert(io->ifd >= 0); - assert(creds); - assert(creds_valid); + 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_data, 0, sizeof(cmsg_data)); + 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_data; - mh.msg_controllen = sizeof(cmsg_data); + mh.msg_control = &cmsg; + mh.msg_controllen = sizeof(cmsg); mh.msg_flags = 0; if ((r = recvmsg(io->ifd, &mh, 0)) >= 0) { - struct cmsghdr *cmsg; + struct cmsghdr *cmh; *creds_valid = 0; - - for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { - - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { + + 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; - assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))); - memcpy(&u, CMSG_DATA(cmsg), sizeof(struct ucred)); + 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 = 1; + *creds_valid = TRUE; break; } } - io->readable = 0; + 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) { - assert(io); - + pa_assert(io); + io->callback = _callback; io->userdata = userdata; } -void pa_iochannel_set_noclose(pa_iochannel*io, int b) { - assert(io); - - io->no_close = b; +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) { - assert(io); - assert(s); - assert(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) { - assert(io); - + pa_assert(io); + return pa_socket_set_rcvbuf(io->ifd, l); } int pa_iochannel_socket_set_sndbuf(pa_iochannel *io, size_t l) { - assert(io); - + pa_assert(io); + return pa_socket_set_sndbuf(io->ofd, l); } pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io) { - assert(io); - + pa_assert(io); + return io->mainloop; } int pa_iochannel_get_recv_fd(pa_iochannel *io) { - assert(io); + pa_assert(io); return io->ifd; } + +int pa_iochannel_get_send_fd(pa_iochannel *io) { + pa_assert(io); + + return io->ofd; +} + +pa_bool_t pa_iochannel_socket_is_local(pa_iochannel *io) { + pa_assert(io); + + if (pa_socket_is_local(io->ifd)) + return TRUE; + + if (io->ifd != io->ofd) + if (pa_socket_is_local(io->ofd)) + return TRUE; + + return FALSE; +} diff --git a/src/pulsecore/iochannel.h b/src/pulsecore/iochannel.h index 0e6d6d3a..9050df90 100644 --- a/src/pulsecore/iochannel.h +++ b/src/pulsecore/iochannel.h @@ -1,31 +1,37 @@ #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 @@ -51,20 +57,20 @@ 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 -int pa_iochannel_creds_supported(pa_iochannel *io); +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, int *creds_valid); +ssize_t pa_iochannel_read_with_creds(pa_iochannel*io, void*data, size_t l, pa_creds *ucred, pa_bool_t *creds_valid); #endif -int pa_iochannel_is_readable(pa_iochannel*io); -int pa_iochannel_is_writable(pa_iochannel*io); -int pa_iochannel_is_hungup(pa_iochannel*io); +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, int b); +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); @@ -77,8 +83,11 @@ void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l); 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_bool_t pa_iochannel_socket_is_local(pa_iochannel *io); + pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io); int pa_iochannel_get_recv_fd(pa_iochannel *io); +int pa_iochannel_get_send_fd(pa_iochannel *io); #endif diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c index a3bca22f..90afaafd 100644 --- a/src/pulsecore/ioline.c +++ b/src/pulsecore/ioline.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -25,14 +25,16 @@ #include <errno.h> #include <stdio.h> -#include <assert.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" @@ -40,11 +42,11 @@ #define READ_SIZE (1024) struct pa_ioline { + PA_REFCNT_DECLARE; + pa_iochannel *io; pa_defer_event *defer_event; pa_mainloop_api *mainloop; - int ref; - int dead; char *wbuf; size_t wbuf_length, wbuf_index, wbuf_valid_length; @@ -52,10 +54,11 @@ struct pa_ioline { char *rbuf; size_t rbuf_length, rbuf_index, rbuf_valid_length; - void (*callback)(pa_ioline*io, const char *s, void *userdata); + pa_ioline_cb_t callback; void *userdata; - int defer_close; + pa_bool_t dead:1; + pa_bool_t defer_close:1; }; static void io_callback(pa_iochannel*io, void *userdata); @@ -63,11 +66,11 @@ 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; - assert(io); - + 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; @@ -77,22 +80,22 @@ pa_ioline* pa_ioline_new(pa_iochannel *io) { l->callback = NULL; l->userdata = NULL; - l->ref = 1; 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; - + l->dead = FALSE; + l->defer_close = FALSE; + pa_iochannel_set_callback(io, io_callback, l); - + return l; } static void ioline_free(pa_ioline *l) { - assert(l); + pa_assert(l); if (l->io) pa_iochannel_free(l->io); @@ -106,27 +109,27 @@ static void ioline_free(pa_ioline *l) { } void pa_ioline_unref(pa_ioline *l) { - assert(l); - assert(l->ref >= 1); + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); - if ((--l->ref) <= 0) + if (PA_REFCNT_DEC(l) <= 0) ioline_free(l); } pa_ioline* pa_ioline_ref(pa_ioline *l) { - assert(l); - assert(l->ref >= 1); + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); - l->ref++; + PA_REFCNT_INC(l); return l; } void pa_ioline_close(pa_ioline *l) { - assert(l); - assert(l->ref >= 1); + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + l->dead = TRUE; - l->dead = 1; - if (l->io) { pa_iochannel_free(l->io); l->io = NULL; @@ -143,41 +146,43 @@ void pa_ioline_close(pa_ioline *l) { void pa_ioline_puts(pa_ioline *l, const char *c) { size_t len; - - assert(l); - assert(l->ref >= 1); - assert(c); + + 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) { - assert(l->wbuf_length >= l->wbuf_valid_length); - + 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); + char *new = pa_xnew(char, 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; } - - assert(l->wbuf_index + l->wbuf_valid_length + len <= l->wbuf_length); - + + 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; @@ -186,18 +191,21 @@ void pa_ioline_puts(pa_ioline *l, const char *c) { } } -void pa_ioline_set_callback(pa_ioline*l, void (*callback)(pa_ioline*io, const char *s, void *userdata), void *userdata) { - assert(l); - assert(l->ref >= 1); - +void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + if (l->dead) + return; + l->callback = callback; l->userdata = userdata; } -static void failure(pa_ioline *l, int process_leftover) { - assert(l); - assert(l->ref >= 1); - assert(!l->dead); +static void failure(pa_ioline *l, pa_bool_t 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 */ @@ -213,22 +221,24 @@ static void failure(pa_ioline *l, int process_leftover) { l->callback(l, NULL, l->userdata); l->callback = NULL; } - + pa_ioline_close(l); } static void scan_for_lines(pa_ioline *l, size_t skip) { - assert(l && l->ref >= 1 && skip < l->rbuf_valid_length); + 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); @@ -240,7 +250,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) { l->rbuf_index = 0; if (l->callback) - l->callback(l, p, l->userdata); + l->callback(l, pa_strip_nl(p), l->userdata); skip = 0; } @@ -253,95 +263,109 @@ static void scan_for_lines(pa_ioline *l, size_t skip) { static int do_write(pa_ioline *l); static int do_read(pa_ioline *l) { - assert(l && l->ref >= 1); + 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); + char *new = pa_xnew(char, 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; - - assert(len >= READ_SIZE); - + + 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) { + + if (r < 0 && errno == EAGAIN) + return 0; + + if (r < 0 && errno != ECONNRESET) { pa_log("read(): %s", pa_cstrerror(errno)); - failure(l, 0); + failure(l, FALSE); } else - failure(l, 1); - + failure(l, TRUE); + 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; - assert(l && l->ref >= 1); + + 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) { - pa_log("write(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); - failure(l, 0); + + if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) <= 0) { + + if (r < 0 && errno == EAGAIN) + return 0; + + if (r < 0 && errno != EPIPE) + pa_log("write(): %s", pa_cstrerror(errno)); + + failure(l, FALSE); + 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) { - assert(l); - assert(l->ref >= 1); + 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); @@ -349,30 +373,37 @@ static void do_work(pa_ioline *l) { do_write(l); if (l->defer_close && !l->wbuf_valid_length) - failure(l, 1); + failure(l, TRUE); pa_ioline_unref(l); } static void io_callback(pa_iochannel*io, void *userdata) { pa_ioline *l = userdata; - assert(io && l && l->ref >= 1); + + 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; - assert(l && l->ref >= 1 && l->mainloop == m && l->defer_event == e); + + 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) { - assert(l); - assert(l->ref >= 1); - - l->defer_close = 1; + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + l->defer_close = TRUE; if (!l->wbuf_valid_length) l->mainloop->defer_enable(l->defer_event, 1); @@ -381,9 +412,9 @@ void pa_ioline_defer_close(pa_ioline *l) { void pa_ioline_printf(pa_ioline *l, const char *format, ...) { char *t; va_list ap; - - assert(l); - assert(l->ref >= 1); + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); va_start(ap, format); t = pa_vsprintf_malloc(format, ap); diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h index e736e2b3..b9a3d9f4 100644 --- a/src/pulsecore/ioline.h +++ b/src/pulsecore/ioline.h @@ -1,21 +1,21 @@ #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 @@ -31,6 +31,8 @@ typedef struct pa_ioline pa_ioline; +typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata); + pa_ioline* pa_ioline_new(pa_iochannel *io); void pa_ioline_unref(pa_ioline *l); pa_ioline* pa_ioline_ref(pa_ioline *l); @@ -43,7 +45,7 @@ void pa_ioline_puts(pa_ioline *s, const char *c); 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); +void pa_ioline_set_callback(pa_ioline*io, pa_ioline_cb_t callback, void *userdata); /* Make sure to close the ioline object as soon as the send buffer is emptied */ void pa_ioline_defer_close(pa_ioline *io); diff --git a/src/pulsecore/ipacl.c b/src/pulsecore/ipacl.c index 36159fab..7b5f865b 100644 --- a/src/pulsecore/ipacl.c +++ b/src/pulsecore/ipacl.c @@ -1,18 +1,19 @@ -/* $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 @@ -43,13 +44,13 @@ #include <arpa/inet.h> #endif -#include "winsock.h" - #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" @@ -59,7 +60,7 @@ struct acl_entry { PA_LLIST_FIELDS(struct acl_entry); - int family; + int family; struct in_addr address_ipv4; struct in6_addr address_ipv6; int bits; @@ -74,11 +75,11 @@ pa_ip_acl* pa_ip_acl_new(const char *s) { char *a; pa_ip_acl *acl; - assert(s); - + 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; @@ -88,7 +89,7 @@ pa_ip_acl* pa_ip_acl_new(const char *s) { *slash = 0; slash++; if (pa_atou(slash, &bits) < 0) { - pa_log("failed to parse number of bits: %s", slash); + pa_log_warn("Failed to parse number of bits: %s", slash); goto fail; } } else @@ -97,23 +98,23 @@ pa_ip_acl* pa_ip_acl_new(const char *s) { 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("number of bits out of range: %i", e.bits); + 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("WARNING: Host part of ACL entry '%s/%u' is not zero!", a, e.bits); - + 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("number of bits out of range: %i", e.bits); + pa_log_warn("Number of bits out of range: %i", e.bits); goto fail; } e.family = AF_INET6; @@ -123,7 +124,7 @@ pa_ip_acl* pa_ip_acl_new(const char *s) { for (i = 0, bits = e.bits; i < 16; i++) { - if (bits >= 8) + if (bits >= 8) bits -= 8; else { if ((uint8_t) ((e.address_ipv6.s6_addr[i]) << bits) != 0) { @@ -135,38 +136,38 @@ pa_ip_acl* pa_ip_acl_new(const char *s) { } if (t) - pa_log_warn("WARNING: Host part of ACL entry '%s/%u' is not zero!", a, e.bits); + pa_log_warn("Host part of ACL entry '%s/%u' is not zero!", a, e.bits); } - + } else { - pa_log("failed to parse address: %s", a); + 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) { - assert(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); } @@ -174,9 +175,9 @@ int pa_ip_acl_check(pa_ip_acl *acl, int fd) { struct sockaddr_storage sa; struct acl_entry *e; socklen_t salen; - - assert(acl); - assert(fd >= 0); + + pa_assert(acl); + pa_assert(fd >= 0); salen = sizeof(sa); if (getpeername(fd, (struct sockaddr*) &sa, &salen) < 0) @@ -190,7 +191,7 @@ int pa_ip_acl_check(pa_ip_acl *acl, int fd) { 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) @@ -198,7 +199,7 @@ int pa_ip_acl_check(pa_ip_acl *acl, int fd) { 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; @@ -211,7 +212,7 @@ int pa_ip_acl_check(pa_ip_acl *acl, int fd) { if (e->bits == 0) return 1; - + for (i = 0, bits = e->bits; i < 16; i++) { if (bits >= 8) { diff --git a/src/pulsecore/ipacl.h b/src/pulsecore/ipacl.h index 7a4540ce..7b7ffa61 100644 --- a/src/pulsecore/ipacl.h +++ b/src/pulsecore/ipacl.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulsecore/llist.h b/src/pulsecore/llist.h index 31279431..46b54eb3 100644 --- a/src/pulsecore/llist.h +++ b/src/pulsecore/llist.h @@ -1,98 +1,107 @@ #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 <assert.h> +#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 +#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 +#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) +#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); \ - assert(_item); \ - _item->prev = _item->next = NULL; \ - } while(0) +#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); \ - assert(_item); \ - if ((_item->next = *_head)) \ - _item->next->prev = _item; \ - _item->prev = NULL; \ - *_head = _item; \ - } while (0) +#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); \ - assert(_item); \ - if (_item->next) \ - _item->next->prev = _item->prev; \ - if (_item->prev) \ - _item->prev->next = _item->next; \ - else {\ - assert(*_head == _item); \ - *_head = _item->next; \ - } \ - _item->next = _item->prev = NULL; \ - } while(0) - -#define PA_LLIST_FIND_HEAD(t,item,head) \ -do { \ - t **_head = (head), *_item = (item); \ - *_head = _item; \ - assert(_head); \ - while ((*_head)->prev) \ - *_head = (*_head)->prev; \ -} while (0) - -#define PA_LLIST_INSERT_AFTER(t,head,a,b) \ -do { \ - t **_head = &(head), *_a = (a), *_b = (b); \ - assert(_b); \ - if (!_a) { \ - if ((_b->next = *_head)) \ - _b->next->prev = _b; \ - _b->prev = NULL; \ - *_head = _b; \ - } else { \ - if ((_b->next = _a->next)) \ - _b->next->prev = _b; \ - _b->prev = _a; \ - _a->next = _b; \ - } \ -} while (0) - +#define PA_LLIST_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 index ce093221..5eda4f65 100644 --- a/src/pulsecore/log.c +++ b/src/pulsecore/log.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,11 +24,11 @@ #include <config.h> #endif -#include <assert.h> #include <stdarg.h> #include <stdio.h> #include <unistd.h> #include <string.h> +#include <errno.h> #ifdef HAVE_SYSLOG_H #include <syslog.h> @@ -37,6 +38,7 @@ #include <pulse/xmalloc.h> #include <pulse/util.h> +#include <pulsecore/macro.h> #include <pulsecore/core-util.h> #include "log.h" @@ -59,25 +61,39 @@ static const int level_to_syslog[] = { }; #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) { - if (log_ident) - pa_xfree(log_ident); - if (log_ident_local) - pa_xfree(log_ident_local); + pa_xfree(log_ident); + pa_xfree(log_ident_local); log_ident = pa_xstrdup(p); - log_ident_local = pa_utf8_to_locale(log_ident); - if (!log_ident_local) + 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) { - assert(l < PA_LOG_LEVEL_MAX); + 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)) { - assert(t == PA_LOG_USER || !func); + pa_assert(t == PA_LOG_USER || !func); + log_target = t; user_log_func = func; } @@ -89,27 +105,34 @@ void pa_log_levelv_meta( const char *func, const char *format, va_list ap) { - + const char *e; - char *text, *t, *n, *location; - - assert(level < PA_LOG_LEVEL_MAX); - assert(format); + char *t, *n; + int saved_errno = errno; + + /* We don't use dynamic memory allocation here to minimize the hit + * in RT threads */ + char text[1024], location[128]; + + pa_assert(level < PA_LOG_LEVEL_MAX); + pa_assert(format); if ((e = getenv(ENV_LOGLEVEL))) maximal_level = atoi(e); - - if (level > maximal_level) + + if (level > maximal_level) { + errno = saved_errno; return; + } - text = pa_vsprintf_malloc(format, ap); + pa_vsnprintf(text, sizeof(text), format, ap); if (getenv(ENV_LOGMETA) && file && line > 0 && func) - location = pa_sprintf_malloc("[%s:%i %s()] ", file, line, func); + pa_snprintf(location, sizeof(location), "[%s:%i %s()] ", file, line, func); else if (file) - location = pa_sprintf_malloc("%s: ", pa_path_get_filename(file)); + pa_snprintf(location, sizeof(location), "%s: ", pa_path_get_filename(file)); else - location = pa_xstrdup(""); + location[0] = 0; if (!pa_utf8_valid(text)) pa_log_level(level, __FILE__": invalid UTF-8 string following below:"); @@ -122,13 +145,13 @@ void pa_log_levelv_meta( if (!*t) continue; - + switch (log_target) { case PA_LOG_STDERR: { const char *prefix = "", *suffix = ""; char *local_t; -#ifndef OS_IS_WIN32 +#ifndef OS_IS_WIN32 /* Yes indeed. Useless, but fun! */ if (isatty(STDERR_FILENO)) { if (level <= PA_LOG_ERROR) { @@ -141,18 +164,20 @@ void pa_log_levelv_meta( } #endif + /* We shouldn't be using dynamic allocation here to + * minimize the hit in RT threads */ local_t = pa_utf8_to_locale(t); if (!local_t) - fprintf(stderr, "%s%s%s%s\n", location, prefix, t, suffix); + fprintf(stderr, "%c: %s%s%s%s\n", level_to_char[level], location, prefix, t, suffix); else { - fprintf(stderr, "%s%s%s%s\n", location, prefix, local_t, suffix); + 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 + +#ifdef HAVE_SYSLOG_H case PA_LOG_SYSLOG: { char *local_t; @@ -167,28 +192,26 @@ void pa_log_levelv_meta( } closelog(); - break; + break; } #endif - + case PA_LOG_USER: { - char *x; + char x[1024]; - x = pa_sprintf_malloc("%s%s", location, t); + pa_snprintf(x, sizeof(x), "%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); + errno = saved_errno; } void pa_log_level_meta( @@ -197,7 +220,7 @@ void pa_log_level_meta( 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); @@ -210,6 +233,7 @@ void pa_log_levelv(pa_log_level_t level, const char *format, va_list 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 index bf0e75f5..2047696e 100644 --- a/src/pulsecore/log.h +++ b/src/pulsecore/log.h @@ -1,21 +1,22 @@ #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 @@ -24,7 +25,7 @@ #include <stdarg.h> #include <stdlib.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> /* A simple logging subsystem */ diff --git a/src/pulsecore/ltdl-helper.c b/src/pulsecore/ltdl-helper.c new file mode 100644 index 00000000..0d4c22f8 --- /dev/null +++ b/src/pulsecore/ltdl-helper.c @@ -0,0 +1,64 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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(symbol); + + if ((f = ((pa_void_func_t) (size_t) lt_dlsym(handle, symbol)))) + return f; + + if (!module) + return NULL; + + /* As the .la files might have been cleansed from the system, we should + * try with the ltdl prefix as well. */ + + sn = pa_sprintf_malloc("%s_LTX_%s", module, symbol); + + for (c = sn; *c; c++) + if (!isalnum(*c)) + *c = '_'; + + f = (pa_void_func_t) (size_t) lt_dlsym(handle, sn); + pa_xfree(sn); + + return f; +} diff --git a/src/pulsecore/ltdl-helper.h b/src/pulsecore/ltdl-helper.h new file mode 100644 index 00000000..ea73de54 --- /dev/null +++ b/src/pulsecore/ltdl-helper.h @@ -0,0 +1,32 @@ +#ifndef foopulsecoreltdlhelperhfoo +#define foopulsecoreltdlhelperhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General 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..fd33b7bb --- /dev/null +++ b/src/pulsecore/macro.h @@ -0,0 +1,224 @@ +#ifndef foopulsemacrohfoo +#define foopulsemacrohfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General 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 <stdio.h> +#include <stdlib.h> + +#include <pulsecore/log.h> +#include <pulse/gccmacro.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#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 + +#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])) + +/* The users of PA_MIN and PA_MAX should be aware that these macros on + * non-GCC executed code with side effects twice. It is thus + * considered misuse to use code with side effects as arguments to MIN + * and MAX. */ + +#ifdef __GNUC__ +#define PA_MAX(a,b) \ + __extension__ ({ typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) +#else +#define PA_MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifdef __GNUC__ +#define PA_MIN(a,b) \ + __extension__ ({ typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) +#else +#define PA_MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifdef __GNUC__ +#define PA_CLAMP(x, low, high) \ + __extension__ ({ typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \ + }) +#else +#define PA_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) +#endif + +#ifdef __GNUC__ +#define PA_CLAMP_UNLIKELY(x, low, high) \ + __extension__ ({ typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + (PA_UNLIKELY(_x > _high) ? _high : (PA_UNLIKELY(_x < _low) ? _low : _x)); \ + }) +#else +#define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x))) +#endif + +/* We don't define a PA_CLAMP_LIKELY here, because it doesn't really + * make sense: we cannot know if it is more likely that the 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 (PA_UNLIKELY(!(expr))) { \ + pa_log_debug("Assertion '%s' failed at %s:%u, function %s.\n", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + return; \ + } \ + } while(FALSE) + +#define pa_return_val_if_fail(expr, val) \ + do { \ + if (PA_UNLIKELY(!(expr))) { \ + pa_log_debug("Assertion '%s' failed at %s:%u, function %s.\n", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + return (val); \ + } \ + } while(FALSE) + +#define pa_return_null_if_fail(expr) pa_return_val_if_fail(expr, NULL) + +/* An assert which guarantees side effects of x, i.e. is never + * optimized away */ +#define pa_assert_se(expr) \ + do { \ + if (PA_UNLIKELY(!(expr))) { \ + pa_log_error("Assertion '%s' failed at %s:%u, function %s(). Aborting.", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + abort(); \ + } \ + } while (FALSE) + +/* An assert that may be optimized away by defining NDEBUG */ +#ifdef NDEBUG +#define pa_assert(expr) do {} while (FALSE) +#else +#define pa_assert(expr) pa_assert_se(expr) +#endif + +#define pa_assert_not_reached() \ + do { \ + pa_log_error("Code should not be reached at %s:%u, function %s(). Aborting.", __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + abort(); \ + } while (FALSE) + +#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 + +#ifdef __GNUC__ + +#define PA_WARN_REFERENCE(sym, msg) \ + __asm__(".section .gnu.warning." #sym); \ + __asm__(".asciz \"" msg "\""); \ + __asm__(".previous") + +#else + +#define PA_WARN_REFERENCE(sym, msg) + +#endif + +#endif diff --git a/src/pulsecore/mcalign.c b/src/pulsecore/mcalign.c index aa2eae46..a03d5ae7 100644 --- a/src/pulsecore/mcalign.c +++ b/src/pulsecore/mcalign.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -25,10 +25,10 @@ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <string.h> #include <pulse/xmalloc.h> +#include <pulsecore/macro.h> #include "mcalign.h" @@ -39,41 +39,41 @@ struct pa_mcalign { pa_mcalign *pa_mcalign_new(size_t base) { pa_mcalign *m; - assert(base); + 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) { - assert(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) { - assert(m); - assert(c); - - assert(c->memblock); - assert(c->length > 0); - - assert(!m->current.memblock); - + 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) { @@ -85,16 +85,16 @@ void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c) { 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 */ - assert(m->leftover.length < m->base); + pa_assert(m->leftover.length < m->base); l = m->base - m->leftover.length; - + if (l > c->length) l = c->length; @@ -108,8 +108,8 @@ void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c) { pa_memblock_release(c->memblock); m->leftover.length += l; - assert(m->leftover.length <= m->base); - assert(m->leftover.length <= pa_memblock_get_length(m->leftover.memblock)); + 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 */ @@ -121,7 +121,7 @@ void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c) { } } else { /* Nothing to merge or copy, just store it */ - + if (c->length >= m->base) m->current = *c; else @@ -132,12 +132,13 @@ void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c) { } int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) { - assert(m); - assert(c); + pa_assert(m); + pa_assert(c); /* First test if there's a leftover memory block available */ if (m->leftover.memblock) { - assert(m->leftover.length > 0 && m->leftover.length <= m->base); + 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) @@ -152,20 +153,20 @@ int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) { 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; - assert(m->current.length >= m->base); + pa_assert(m->current.length >= m->base); /* The length of the returned memory block */ l = m->current.length; l /= m->base; l *= m->base; - assert(l > 0); + pa_assert(l > 0); /* Prepare the returned block */ *c = m->current; @@ -173,7 +174,7 @@ int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) { c->length = l; /* Drop that from the current memory block */ - assert(l <= m->current.length); + pa_assert(l <= m->current.length); m->current.index += l; m->current.length -= l; @@ -182,29 +183,36 @@ int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) { pa_memblock_unref(m->current.memblock); else { /* Move the raimainder to leftover */ - assert(m->current.length < m->base && !m->leftover.memblock); + 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) { - assert(m); - assert(l > 0); + pa_assert(m); + pa_assert(l > 0); + + pa_assert(!m->current.memblock); - assert(!m->current.memblock); - if (m->leftover.memblock) l += m->leftover.length; - + return (l/m->base)*m->base; } + +void pa_mcalign_flush(pa_mcalign *m) { + pa_memchunk chunk; + pa_assert(m); + + while (pa_mcalign_pop(m, &chunk) >= 0) + pa_memblock_unref(chunk.memblock); +} diff --git a/src/pulsecore/mcalign.h b/src/pulsecore/mcalign.h index 94e99e21..e82eb007 100644 --- a/src/pulsecore/mcalign.h +++ b/src/pulsecore/mcalign.h @@ -1,21 +1,21 @@ #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 @@ -41,11 +41,11 @@ * for (;;) { * pa_memchunk input; * - * ... fill input ... + * ... fill input ... * * pa_mcalign_push(m, &input); * pa_memblock_unref(input.memblock); - * + * * for (;;) { * pa_memchunk output; * @@ -77,4 +77,7 @@ int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c); /* If we pass l bytes in now, how many bytes would we get out? */ size_t pa_mcalign_csize(pa_mcalign *m, size_t l); +/* Flush what's still stored in the aligner */ +void pa_mcalign_flush(pa_mcalign *m); + #endif diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c index f11a7174..c2ee1360 100644 --- a/src/pulsecore/memblock.c +++ b/src/pulsecore/memblock.c @@ -1,18 +1,19 @@ -/* $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. - + 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 @@ -25,9 +26,10 @@ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <string.h> #include <unistd.h> +#include <signal.h> +#include <errno.h> #include <pulse/xmalloc.h> #include <pulse/def.h> @@ -35,13 +37,19 @@ #include <pulsecore/shm.h> #include <pulsecore/log.h> #include <pulsecore/hashmap.h> -#include <pulsecore/mutex.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) +/* We can allocate 64*1024*1024 bytes at maximum. That's 64MB. Please + * note that the footprint is usually much smaller, since the data is + * stored in SHM and our OS does not commit the memory before we use + * it for the first time. */ +#define PA_MEMPOOL_SLOTS_MAX 1024 +#define PA_MEMPOOL_SLOT_SIZE (64*1024) #define PA_MEMEXPORT_SLOTS_MAX 128 @@ -53,20 +61,22 @@ struct pa_memblock { pa_mempool *pool; pa_memblock_type_t type; - int read_only; /* boolean */ - + + pa_bool_t read_only:1; + pa_bool_t is_silence:1; + pa_atomic_ptr_t data; size_t length; - pa_atomic_int_t n_acquired; - pa_atomic_int_t please_signal; + 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; @@ -82,7 +92,7 @@ struct pa_memimport_segment { struct pa_memimport { pa_mutex *mutex; - + pa_mempool *pool; pa_hashmap *segments; pa_hashmap *blocks; @@ -103,7 +113,7 @@ struct memexport_slot { 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); @@ -119,46 +129,43 @@ struct pa_memexport { 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_cond *cond; - + pa_shm memory; size_t block_size; unsigned n_blocks; - pa_atomic_int_t n_init; + 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) { - assert(b); - assert(b->pool); + pa_assert(b); + pa_assert(b->pool); pa_atomic_inc(&b->pool->stat.n_allocated); - pa_atomic_add(&b->pool->stat.allocated_size, (int) b->length); + pa_atomic_add(&b->pool->stat.allocated_size, b->length); pa_atomic_inc(&b->pool->stat.n_accumulated); - pa_atomic_add(&b->pool->stat.accumulated_size, (int) b->length); + 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, (int) b->length); + pa_atomic_add(&b->pool->stat.imported_size, b->length); } pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]); @@ -167,21 +174,21 @@ static void stat_add(pa_memblock*b) { /* No lock necessary */ static void stat_remove(pa_memblock *b) { - assert(b); - assert(b->pool); + 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); - assert(pa_atomic_load(&b->pool->stat.n_allocated) > 0); - assert(pa_atomic_load(&b->pool->stat.allocated_size) >= (int) b->length); - pa_atomic_dec(&b->pool->stat.n_allocated); - pa_atomic_add(&b->pool->stat.allocated_size, - (int) b->length); + pa_atomic_sub(&b->pool->stat.allocated_size, b->length); if (b->type == PA_MEMBLOCK_IMPORTED) { - assert(pa_atomic_load(&b->pool->stat.n_imported) > 0); - assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length); - + 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_add(&b->pool->stat.imported_size, - (int) b->length); + pa_atomic_sub(&b->pool->stat.imported_size, b->length); } pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]); @@ -192,10 +199,10 @@ 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; - - assert(p); - assert(length > 0); - + + pa_assert(p); + pa_assert(length); + if (!(b = pa_memblock_new_pool(p, length))) b = memblock_new_appended(p, length); @@ -206,19 +213,24 @@ pa_memblock *pa_memblock_new(pa_mempool *p, size_t length) { static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length) { pa_memblock *b; - assert(p); - assert(length > 0); + pa_assert(p); + pa_assert(length); + + /* If -1 is passed as length we choose the size for the caller. */ - b = pa_xmalloc(sizeof(pa_memblock) + length); + if (length == (size_t) -1) + length = p->block_size - 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 + sizeof(pa_memblock)); + b->read_only = b->is_silence = FALSE; + pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock))); b->length = length; pa_atomic_store(&b->n_acquired, 0); pa_atomic_store(&b->please_signal, 0); - + stat_add(b); return b; } @@ -226,40 +238,39 @@ static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length) { /* No lock necessary */ static struct mempool_slot* mempool_allocate_slot(pa_mempool *p) { struct mempool_slot *slot; - assert(p); + 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_log_info("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) { - assert(slot); - - return (uint8_t*) slot + sizeof(struct mempool_slot); +/* No lock necessary, totally redundant anyway */ +static inline void* mempool_slot_data(struct mempool_slot *slot) { + return slot; } /* No lock necessary */ static unsigned mempool_slot_idx(pa_mempool *p, void *ptr) { - assert(p); + pa_assert(p); - assert((uint8_t*) ptr >= (uint8_t*) p->memory.ptr); - assert((uint8_t*) ptr < (uint8_t*) p->memory.ptr + p->memory.size); + 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; } @@ -279,36 +290,44 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) { pa_memblock *b = NULL; struct mempool_slot *slot; - assert(p); - assert(length > 0); + pa_assert(p); + pa_assert(length); + + /* 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 - sizeof(struct mempool_slot) >= sizeof(pa_memblock) + length) { + if (p->block_size >= PA_ALIGN(sizeof(pa_memblock)) + length) { if (!(slot = mempool_allocate_slot(p))) return NULL; - + b = mempool_slot_data(slot); b->type = PA_MEMBLOCK_POOL; - pa_atomic_ptr_store(&b->data, (uint8_t*) b + sizeof(pa_memblock)); - - } else if (p->block_size - sizeof(struct mempool_slot) >= length) { + pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock))); + + } else if (p->block_size >= length) { if (!(slot = mempool_allocate_slot(p))) return NULL; - - b = pa_xnew(pa_memblock, 1); + + 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: %u > %u", length, p->block_size - sizeof(struct mempool_slot)); + pa_log_debug("Memory block too large for pool: %lu > %lu", (unsigned long) length, (unsigned long) p->block_size); pa_atomic_inc(&p->stat.n_too_large_for_pool); return NULL; } PA_REFCNT_INIT(b); b->pool = p; - b->read_only = 0; + b->read_only = b->is_silence = FALSE; b->length = length; pa_atomic_store(&b->n_acquired, 0); pa_atomic_store(&b->please_signal, 0); @@ -318,18 +337,21 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) { } /* No lock necessary */ -pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, int read_only) { +pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, pa_bool_t read_only) { pa_memblock *b; - assert(p); - assert(d); - assert(length > 0); + pa_assert(p); + pa_assert(d); + pa_assert(length != (size_t) -1); + pa_assert(length); - b = pa_xnew(pa_memblock, 1); + 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; + b->is_silence = FALSE; pa_atomic_ptr_store(&b->data, d); b->length = length; pa_atomic_store(&b->n_acquired, 0); @@ -340,24 +362,27 @@ pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, int re } /* 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 *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, pa_free_cb_t free_cb, pa_bool_t read_only) { pa_memblock *b; - assert(p); - assert(d); - assert(length > 0); - assert(free_cb); - - b = pa_xnew(pa_memblock, 1); + pa_assert(p); + pa_assert(d); + pa_assert(length); + pa_assert(length != (size_t) -1); + pa_assert(free_cb); + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); PA_REFCNT_INIT(b); b->pool = p; b->type = PA_MEMBLOCK_USER; b->read_only = read_only; + b->is_silence = FALSE; pa_atomic_ptr_store(&b->data, d); b->length = length; pa_atomic_store(&b->n_acquired, 0); pa_atomic_store(&b->please_signal, 0); - + b->per_type.user.free_cb = free_cb; stat_add(b); @@ -365,103 +390,129 @@ pa_memblock *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, void (* } /* No lock necessary */ -int pa_memblock_is_read_only(pa_memblock *b) { - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); +pa_bool_t 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 */ +pa_bool_t pa_memblock_is_silence(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->is_silence; +} + +/* No lock necessary */ +void pa_memblock_set_is_silence(pa_memblock *b, pa_bool_t v) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + b->is_silence = v; +} + +/* No lock necessary */ +pa_bool_t pa_memblock_ref_is_one(pa_memblock *b) { + int r; + pa_assert(b); + + pa_assert_se((r = PA_REFCNT_VALUE(b)) > 0); + + return r == 1; +} + +/* No lock necessary */ void* pa_memblock_acquire(pa_memblock *b) { - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); + 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; - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); - + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + r = pa_atomic_dec(&b->n_acquired); - assert(r >= 1); - - if (r == 1 && pa_atomic_load(&b->please_signal)) { - pa_mempool *p = b->pool; - /* Signal a waiting thread that this memblock is no longer used */ - pa_mutex_lock(p->mutex); - pa_cond_signal(p->cond, 1); - pa_mutex_unlock(p->mutex); - } + 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) { - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); return b->length; } pa_mempool* pa_memblock_get_pool(pa_memblock *b) { - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); return b->pool; } /* No lock necessary */ pa_memblock* pa_memblock_ref(pa_memblock*b) { - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); PA_REFCNT_INC(b); return b; } static void memblock_free(pa_memblock *b) { - assert(b); - - assert(pa_atomic_load(&b->n_acquired) == 0); + pa_assert(b); + + pa_assert(pa_atomic_load(&b->n_acquired) == 0); stat_remove(b); switch (b->type) { case PA_MEMBLOCK_USER : - assert(b->per_type.user.free_cb); + 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 : - pa_xfree(b); + 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; - assert(segment); + pa_assert(segment); import = segment->import; - assert(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); - pa_xfree(b); + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); break; } @@ -471,7 +522,7 @@ static void memblock_free(pa_memblock *b) { int call_free; slot = mempool_slot_by_ptr(b->pool, pa_atomic_ptr_load(&b->data)); - assert(slot); + pa_assert(slot); call_free = b->type == PA_MEMBLOCK_POOL_EXTERNAL; @@ -480,23 +531,24 @@ static void memblock_free(pa_memblock *b) { * the free list fails */ while (pa_flist_push(b->pool->free_slots, slot) < 0) ; - + if (call_free) - pa_xfree(b); + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); break; } case PA_MEMBLOCK_TYPE_MAX: default: - abort(); + pa_assert_not_reached(); } } /* No lock necessary */ void pa_memblock_unref(pa_memblock*b) { - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); if (PA_REFCNT_DEC(b) > 0) return; @@ -506,7 +558,7 @@ void pa_memblock_unref(pa_memblock*b) { /* Self locked */ static void memblock_wait(pa_memblock *b) { - assert(b); + pa_assert(b); if (pa_atomic_load(&b->n_acquired) > 0) { /* We need to wait until all threads gave up access to the @@ -515,10 +567,8 @@ static void memblock_wait(pa_memblock *b) { pa_atomic_inc(&b->please_signal); - pa_mutex_lock(b->pool->mutex); while (pa_atomic_load(&b->n_acquired) > 0) - pa_cond_wait(b->pool->cond, b->pool->mutex); - pa_mutex_unlock(b->pool->mutex); + pa_semaphore_wait(b->pool->semaphore); pa_atomic_dec(&b->please_signal); } @@ -526,23 +576,23 @@ static void memblock_wait(pa_memblock *b) { /* No lock necessary. This function is not multiple caller safe! */ static void memblock_make_local(pa_memblock *b) { - assert(b); + pa_assert(b); pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]); - if (b->length <= b->pool->block_size - sizeof(struct mempool_slot)) { + if (b->length <= b->pool->block_size) { struct mempool_slot *slot; if ((slot = mempool_allocate_slot(b->pool))) { void *new_data; /* We can move it into a local pool, perfect! */ - + new_data = mempool_slot_data(slot); memcpy(new_data, pa_atomic_ptr_load(&b->data), b->length); pa_atomic_ptr_store(&b->data, new_data); b->type = PA_MEMBLOCK_POOL_EXTERNAL; - b->read_only = 0; + b->read_only = FALSE; goto finish; } @@ -553,83 +603,85 @@ static void memblock_make_local(pa_memblock *b) { 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; + b->read_only = FALSE; finish: pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]); pa_atomic_inc(&b->pool->stat.n_accumulated_by_type[b->type]); - memblock_wait(b); } /* No lock necessary. This function is not multiple caller safe*/ void pa_memblock_unref_fixed(pa_memblock *b) { - assert(b); - assert(PA_REFCNT_VALUE(b) > 0); - assert(b->type == PA_MEMBLOCK_FIXED); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b->type == PA_MEMBLOCK_FIXED); - if (PA_REFCNT_DEC(b) > 0) + if (PA_REFCNT_VALUE(b) > 1) memblock_make_local(b); - else - memblock_free(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; - - assert(b); - assert(b->type == PA_MEMBLOCK_IMPORTED); - assert(pa_atomic_load(&b->pool->stat.n_imported) > 0); - assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length); + 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_add(&b->pool->stat.imported_size, (int) - b->length); + pa_atomic_sub(&b->pool->stat.imported_size, b->length); seg = b->per_type.imported.segment; - assert(seg); - assert(seg->import); + 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) + if (-- seg->n_blocks <= 0) { + pa_mutex_unlock(seg->import->mutex); segment_detach(seg); - - pa_mutex_unlock(seg->import->mutex); + } else + pa_mutex_unlock(seg->import->mutex); } -pa_mempool* pa_mempool_new(int shared) { - size_t ps; +pa_mempool* pa_mempool_new(pa_bool_t shared) { pa_mempool *p; p = pa_xnew(pa_mempool, 1); - p->mutex = pa_mutex_new(1); - p->cond = pa_cond_new(); - -#ifdef HAVE_SYSCONF - ps = (size_t) sysconf(_SC_PAGESIZE); -#elif defined(PAGE_SIZE) - ps = (size_t) PAGE_SIZE; -#else - ps = 4096; /* Let's hope it's like x86. */ -#endif + p->mutex = pa_mutex_new(TRUE, TRUE); + p->semaphore = pa_semaphore_new(0); - p->block_size = (PA_MEMPOOL_SLOT_SIZE/ps)*ps; + p->block_size = PA_PAGE_ALIGN(PA_MEMPOOL_SLOT_SIZE); + if (p->block_size < PA_PAGE_SIZE) + p->block_size = PA_PAGE_SIZE; - if (p->block_size < ps) - p->block_size = ps; - p->n_blocks = PA_MEMPOOL_SLOTS_MAX; - assert(p->block_size > 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; @@ -637,7 +689,7 @@ pa_mempool* pa_mempool_new(int shared) { 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); @@ -647,7 +699,7 @@ pa_mempool* pa_mempool_new(int shared) { } void pa_mempool_free(pa_mempool *p) { - assert(p); + pa_assert(p); pa_mutex_lock(p->mutex); @@ -659,32 +711,41 @@ void pa_mempool_free(pa_mempool *p) { pa_mutex_unlock(p->mutex); - if (pa_atomic_load(&p->stat.n_allocated) > 0) - pa_log_warn("WARNING! Memory pool destroyed but not all memory blocks freed!"); - 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_cond_free(p->cond); - + pa_semaphore_free(p->semaphore); + pa_xfree(p); } /* No lock necessary */ const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p) { - assert(p); + pa_assert(p); return &p->stat; } /* No lock necessary */ +size_t pa_mempool_block_size_max(pa_mempool *p) { + pa_assert(p); + + return p->block_size - PA_ALIGN(sizeof(pa_memblock)); +} + +/* No lock necessary */ void pa_mempool_vacuum(pa_mempool *p) { struct mempool_slot *slot; pa_flist *list; - - assert(p); + + pa_assert(p); list = pa_flist_new(p->n_blocks*2); @@ -693,9 +754,7 @@ void pa_mempool_vacuum(pa_mempool *p) { ; while ((slot = pa_flist_pop(list))) { - pa_shm_punch(&p->memory, - (uint8_t*) slot - (uint8_t*) p->memory.ptr + sizeof(struct mempool_slot), - p->block_size - sizeof(struct mempool_slot)); + pa_shm_punch(&p->memory, (uint8_t*) slot - (uint8_t*) p->memory.ptr, p->block_size); while (pa_flist_push(p->free_slots, slot)) ; @@ -706,19 +765,19 @@ void pa_mempool_vacuum(pa_mempool *p) { /* No lock necessary */ int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) { - assert(p); + 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) { - assert(p); +pa_bool_t pa_mempool_is_shared(pa_mempool *p) { + pa_assert(p); return !!p->memory.shared; } @@ -727,11 +786,11 @@ int pa_mempool_is_shared(pa_mempool *p) { pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata) { pa_memimport *i; - assert(p); - assert(cb); - + pa_assert(p); + pa_assert(cb); + i = pa_xnew(pa_memimport, 1); - i->mutex = pa_mutex_new(0); + i->mutex = pa_mutex_new(TRUE, TRUE); i->pool = p; i->segments = pa_hashmap_new(NULL, NULL); i->blocks = pa_hashmap_new(NULL, NULL); @@ -755,7 +814,7 @@ static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id) { return NULL; seg = pa_xnew(pa_memimport_segment, 1); - + if (pa_shm_attach_ro(&seg->memory, shm_id) < 0) { pa_xfree(seg); return NULL; @@ -763,14 +822,14 @@ static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id) { 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) { - assert(seg); + pa_assert(seg); pa_hashmap_remove(seg->import->segments, PA_UINT32_TO_PTR(seg->memory.id)); pa_shm_free(&seg->memory); @@ -781,16 +840,16 @@ static void segment_detach(pa_memimport_segment *seg) { void pa_memimport_free(pa_memimport *i) { pa_memexport *e; pa_memblock *b; - - assert(i); + + pa_assert(i); pa_mutex_lock(i->mutex); while ((b = pa_hashmap_get_first(i->blocks))) memblock_replace_import(b); - assert(pa_hashmap_size(i->segments) == 0); - + pa_assert(pa_hashmap_size(i->segments) == 0); + pa_mutex_unlock(i->mutex); pa_mutex_lock(i->pool->mutex); @@ -800,14 +859,14 @@ void pa_memimport_free(pa_memimport *i) { 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); } @@ -815,26 +874,29 @@ 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) { pa_memblock *b = NULL; pa_memimport_segment *seg; - - assert(i); + + 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 = 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; - b = pa_xnew(pa_memblock, 1); + 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; + b->read_only = TRUE; + b->is_silence = FALSE; pa_atomic_ptr_store(&b->data, (uint8_t*) seg->memory.ptr + offset); b->length = size; pa_atomic_store(&b->n_acquired, 0); @@ -845,44 +907,48 @@ pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_i 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); - + stat_add(b); + return b; } int pa_memimport_process_revoke(pa_memimport *i, uint32_t id) { pa_memblock *b; - assert(i); + int ret = 0; + pa_assert(i); pa_mutex_lock(i->mutex); - if (!(b = pa_hashmap_get(i->blocks, PA_UINT32_TO_PTR(id)))) - return -1; - + 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 0; + 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; - - assert(p); - assert(cb); + + pa_assert(p); + pa_assert(cb); if (!p->memory.shared) return NULL; - + e = pa_xnew(pa_memexport, 1); - e->mutex = pa_mutex_new(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); @@ -893,12 +959,11 @@ pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void 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) { - assert(e); + pa_assert(e); pa_mutex_lock(e->mutex); while (e->used_slots) @@ -908,18 +973,19 @@ void pa_memexport_free(pa_memexport *e) { 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; - - assert(e); + + pa_assert(e); pa_mutex_lock(e->mutex); - + if (id >= e->n_init) goto fail; @@ -933,37 +999,37 @@ int pa_memexport_process_release(pa_memexport *e, uint32_t 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); */ - assert(pa_atomic_load(&e->pool->stat.n_exported) > 0); - assert(pa_atomic_load(&e->pool->stat.exported_size) >= (int) b->length); - + 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_add(&e->pool->stat.exported_size, (int) -b->length); - + 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; - assert(e); - assert(i); + 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; @@ -980,13 +1046,13 @@ static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i) { static pa_memblock *memblock_shared_copy(pa_mempool *p, pa_memblock *b) { pa_memblock *n; - assert(p); - assert(b); - + pa_assert(p); + pa_assert(b); + if (b->type == PA_MEMBLOCK_IMPORTED || b->type == PA_MEMBLOCK_POOL || b->type == PA_MEMBLOCK_POOL_EXTERNAL) { - assert(b->pool == p); + pa_assert(b->pool == p); return pa_memblock_ref(b); } @@ -1002,25 +1068,24 @@ int pa_memexport_put(pa_memexport *e, pa_memblock *b, uint32_t *block_id, uint32 pa_shm *memory; struct memexport_slot *slot; void *data; - size_t length; - - assert(e); - assert(b); - assert(block_id); - assert(shm_id); - assert(offset); - assert(size); - assert(b->pool == e->pool); + + 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) + } else if (e->n_init < PA_MEMEXPORT_SLOTS_MAX) slot = &e->slots[e->n_init++]; else { pa_mutex_unlock(e->mutex); @@ -1038,25 +1103,25 @@ int pa_memexport_put(pa_memexport *e, pa_memblock *b, uint32_t *block_id, uint32 data = pa_memblock_acquire(b); if (b->type == PA_MEMBLOCK_IMPORTED) { - assert(b->per_type.imported.segment); + pa_assert(b->per_type.imported.segment); memory = &b->per_type.imported.segment->memory; } else { - assert(b->type == PA_MEMBLOCK_POOL || b->type == PA_MEMBLOCK_POOL_EXTERNAL); - assert(b->pool); + pa_assert(b->type == PA_MEMBLOCK_POOL || b->type == PA_MEMBLOCK_POOL_EXTERNAL); + pa_assert(b->pool); memory = &b->pool->memory; } - - assert(data >= memory->ptr); - assert((uint8_t*) data + length <= (uint8_t*) memory->ptr + memory->size); - + + 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 = length; + *size = b->length; pa_memblock_release(b); - + pa_atomic_inc(&e->pool->stat.n_exported); - pa_atomic_add(&e->pool->stat.exported_size, (int) length); + pa_atomic_add(&e->pool->stat.exported_size, b->length); return 0; } diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h index 9937818f..efe55b02 100644 --- a/src/pulsecore/memblock.h +++ b/src/pulsecore/memblock.h @@ -1,21 +1,22 @@ #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 @@ -28,6 +29,7 @@ #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 @@ -38,7 +40,7 @@ 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_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 */ @@ -60,20 +62,20 @@ typedef void (*pa_memexport_revoke_cb_t)(pa_memexport *e, uint32_t block_id, voi * 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_int_t n_allocated; - pa_atomic_int_t n_accumulated; - pa_atomic_int_t n_imported; - pa_atomic_int_t n_exported; - pa_atomic_int_t allocated_size; - pa_atomic_int_t accumulated_size; - pa_atomic_int_t imported_size; - pa_atomic_int_t exported_size; - - pa_atomic_int_t n_too_large_for_pool; - pa_atomic_int_t n_pool_full; - - pa_atomic_int_t n_allocated_by_type[PA_MEMBLOCK_TYPE_MAX]; - pa_atomic_int_t n_accumulated_by_type[PA_MEMBLOCK_TYPE_MAX]; + 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 */ @@ -83,13 +85,13 @@ pa_memblock *pa_memblock_new(pa_mempool *, size_t length); 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); +pa_memblock *pa_memblock_new_user(pa_mempool *, void *data, size_t length, pa_free_cb_t free_cb, pa_bool_t 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); +pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, pa_bool_t read_only); void pa_memblock_unref(pa_memblock*b); pa_memblock* pa_memblock_ref(pa_memblock*b); @@ -102,19 +104,26 @@ 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); +pa_bool_t pa_memblock_is_read_only(pa_memblock *b); +pa_bool_t pa_memblock_is_silence(pa_memblock *b); +pa_bool_t pa_memblock_ref_is_one(pa_memblock *b); +void pa_memblock_set_is_silence(pa_memblock *b, pa_bool_t v); + 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); +pa_mempool* pa_mempool_new(pa_bool_t 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); +pa_bool_t 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); diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index dab44dc3..a9f28a07 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -26,7 +26,6 @@ #include <sys/time.h> #include <time.h> #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> @@ -34,23 +33,30 @@ #include <pulsecore/log.h> #include <pulsecore/mcalign.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> #include "memblockq.h" -struct memblock_list { - struct memblock_list *next, *prev; +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 memblock_list *blocks, *blocks_tail; + struct list_item *blocks, *blocks_tail; + struct list_item *current_read, *current_write; unsigned n_blocks; - size_t maxlength, tlength, base, prebuf, minreq; + size_t maxlength, tlength, base, prebuf, minreq, maxrewind; int64_t read_index, write_index; - enum { PREBUF, RUNNING } state; - pa_memblock *silence; + pa_bool_t in_prebuf; + pa_memchunk silence; pa_mcalign *mcalign; + int64_t missing; + size_t requested; }; pa_memblockq* pa_memblockq_new( @@ -60,93 +66,165 @@ pa_memblockq* pa_memblockq_new( size_t base, size_t prebuf, size_t minreq, - pa_memblock *silence) { - + size_t maxrewind, + pa_memchunk *silence) { + pa_memblockq* bq; - - assert(base > 0); - assert(maxlength >= base); - + + pa_assert(base > 0); + bq = pa_xnew(pa_memblockq, 1); bq->blocks = bq->blocks_tail = NULL; + bq->current_read = bq->current_write = 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); + pa_log_debug("memblockq requested: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu", + (unsigned long) maxlength, (unsigned long) tlength, (unsigned long) base, (unsigned long) prebuf, (unsigned long) minreq, (unsigned long) maxrewind); - bq->maxlength = ((maxlength+base-1)/base)*base; - assert(bq->maxlength >= base); + bq->missing = bq->requested = bq->maxlength = bq->tlength = bq->prebuf = bq->minreq = bq->maxrewind = 0; + bq->in_prebuf = TRUE; - bq->tlength = ((tlength+base-1)/base)*base; - if (!bq->tlength || bq->tlength >= bq->maxlength) - bq->tlength = bq->maxlength; + pa_memblockq_set_maxlength(bq, maxlength); + pa_memblockq_set_tlength(bq, tlength); + pa_memblockq_set_prebuf(bq, prebuf); + pa_memblockq_set_minreq(bq, minreq); + pa_memblockq_set_maxrewind(bq, maxrewind); + + pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu", + (unsigned long) bq->maxlength, (unsigned long) bq->tlength, (unsigned long) bq->base, (unsigned long) bq->prebuf, (unsigned long) bq->minreq, (unsigned long) bq->maxrewind); + + if (silence) { + bq->silence = *silence; + pa_memblock_ref(bq->silence.memblock); + } else + pa_memchunk_reset(&bq->silence); + + bq->mcalign = pa_mcalign_new(bq->base); - bq->prebuf = (prebuf == (size_t) -1) ? bq->tlength/2 : prebuf; - bq->prebuf = ((bq->prebuf+base-1)/base)*base; - if (bq->prebuf > bq->maxlength) - bq->prebuf = bq->maxlength; - - bq->minreq = (minreq/base)*base; - - if (bq->minreq > bq->tlength - bq->prebuf) - bq->minreq = bq->tlength - bq->prebuf; - - if (!bq->minreq) - bq->minreq = 1; - - 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->state = bq->prebuf ? PREBUF : RUNNING; - bq->silence = silence ? pa_memblock_ref(silence) : NULL; - bq->mcalign = NULL; - return bq; } void pa_memblockq_free(pa_memblockq* bq) { - assert(bq); + pa_assert(bq); pa_memblockq_flush(bq); - if (bq->silence) - pa_memblock_unref(bq->silence); + if (bq->silence.memblock) + pa_memblock_unref(bq->silence.memblock); if (bq->mcalign) pa_mcalign_free(bq->mcalign); - + pa_xfree(bq); } -static void drop_block(pa_memblockq *bq, struct memblock_list *q) { - assert(bq); - assert(q); +static void fix_current_read(pa_memblockq *bq) { + pa_assert(bq); + + if (PA_UNLIKELY(!bq->blocks)) { + bq->current_read = NULL; + return; + } + + if (PA_UNLIKELY(!bq->current_read)) + bq->current_read = bq->blocks; + + /* Scan left */ + while (PA_UNLIKELY(bq->current_read->index > bq->read_index)) + + if (bq->current_read->prev) + bq->current_read = bq->current_read->prev; + else + break; + + /* Scan right */ + while (PA_LIKELY(bq->current_read != NULL) && PA_UNLIKELY(bq->current_read->index + (int64_t) bq->current_read->chunk.length <= bq->read_index)) + bq->current_read = bq->current_read->next; + + /* At this point current_read will either point at or left of the + next block to play. It may be NULL in case everything in + the queue was already played */ +} + +static void fix_current_write(pa_memblockq *bq) { + pa_assert(bq); + + if (PA_UNLIKELY(!bq->blocks)) { + bq->current_write = NULL; + return; + } + + if (PA_UNLIKELY(!bq->current_write)) + bq->current_write = bq->blocks_tail; + + /* Scan right */ + while (PA_UNLIKELY(bq->current_write->index + (int64_t) bq->current_write->chunk.length <= bq->write_index)) + + if (bq->current_write->next) + bq->current_write = bq->current_write->next; + else + break; + + /* Scan left */ + while (PA_LIKELY(bq->current_write != NULL) && PA_UNLIKELY(bq->current_write->index > bq->write_index)) + bq->current_write = bq->current_write->prev; + + /* At this point current_write will either point at or right of + the next block to write data to. It may be NULL in case + everything in the queue is still to be played */ +} + +static void drop_block(pa_memblockq *bq, struct list_item *q) { + pa_assert(bq); + pa_assert(q); + + pa_assert(bq->n_blocks >= 1); - assert(bq->n_blocks >= 1); - if (q->prev) q->prev->next = q->next; - else + else { + pa_assert(bq->blocks == q); bq->blocks = q->next; - + } + if (q->next) q->next->prev = q->prev; - else + else { + pa_assert(bq->blocks_tail == q); bq->blocks_tail = q->prev; + } + + if (bq->current_write == q) + bq->current_write = q->prev; + + if (bq->current_read == q) + bq->current_read = q->next; pa_memblock_unref(q->chunk.memblock); - pa_xfree(q); + + if (pa_flist_push(PA_STATIC_FLIST_GET(list_items), q) < 0) + pa_xfree(q); bq->n_blocks--; } -static int can_push(pa_memblockq *bq, size_t l) { +static void drop_backlog(pa_memblockq *bq) { + int64_t boundary; + pa_assert(bq); + + boundary = bq->read_index - bq->maxrewind; + + while (bq->blocks && (bq->blocks->index + (int64_t) bq->blocks->chunk.length <= boundary)) + drop_block(bq, bq->blocks); +} + +static pa_bool_t can_push(pa_memblockq *bq, size_t l) { int64_t end; - assert(bq); + pa_assert(bq); if (bq->read_index > bq->write_index) { size_t d = bq->read_index - bq->write_index; @@ -154,29 +232,29 @@ static int can_push(pa_memblockq *bq, size_t l) { if (l > d) l -= d; else - return 1; + return TRUE; } - end = bq->blocks_tail ? bq->blocks_tail->index + bq->blocks_tail->chunk.length : 0; + end = bq->blocks_tail ? bq->blocks_tail->index + (int64_t) bq->blocks_tail->chunk.length : bq->write_index; /* Make sure that the list doesn't get too long */ - if (bq->write_index + (int64_t)l > end) + if (bq->write_index + (int64_t) l > end) if (bq->write_index + l - bq->read_index > bq->maxlength) - return 0; + return FALSE; - return 1; + return TRUE; } int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { - - struct memblock_list *q, *n; + struct list_item *q, *n; pa_memchunk chunk; - - assert(bq); - assert(uchunk); - assert(uchunk->memblock); - assert(uchunk->length > 0); - assert(uchunk->index + uchunk->length <= pa_memblock_get_length(uchunk->memblock)); + 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; @@ -184,36 +262,35 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { 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 */ + fix_current_write(bq); + q = bq->current_write; - size_t d = bq->read_index - bq->write_index; + /* First we advance the q pointer right of where we want to + * write to */ - if (chunk.length > d) { - chunk.index += d; - chunk.length -= d; - bq->write_index = bq->read_index; - } else { - /* We drop the incoming data completely */ - bq->write_index += chunk.length; - return 0; - } + if (q) { + while (bq->write_index + (int64_t) chunk.length > q->index) + if (q->next) + q = q->next; + else + break; } - + + if (!q) + q = bq->blocks_tail; + /* We go from back to front to look for the right place to add * this new entry. Drop data we will overwrite on the way */ - q = bq->blocks_tail; while (q) { - if (bq->write_index >= q->index + (int64_t)q->chunk.length) + 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) { + 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 && @@ -221,7 +298,7 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { /* This entry is fully replaced by the new entry, so let's drop it */ - struct memblock_list *p; + struct list_item *p; p = q; q = q->prev; drop_block(bq, p); @@ -232,17 +309,19 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { if (bq->write_index + chunk.length < q->index + q->chunk.length) { /* We need to save the end of this memchunk */ - struct memblock_list *p; + struct list_item *p; size_t d; /* Create a new list entry for the end of thie memchunk */ - p = pa_xnew(struct memblock_list, 1); + 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; - assert(d > 0); + pa_assert(d > 0); /* Drop it from the new entry */ p->index = q->index + d; @@ -261,7 +340,7 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { /* Truncate the chunk */ if (!(q->chunk.length = bq->write_index - q->index)) { - struct memblock_list *p; + struct list_item *p; p = q; q = q->prev; drop_block(bq, p); @@ -272,41 +351,41 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { } else { size_t d; - assert(bq->write_index + (int64_t)chunk.length > q->index && + 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) { - assert(bq->write_index >= q->index + (int64_t)q->chunk.length); - assert(!q->next || (bq->write_index + (int64_t)chunk.length <= q->next->index)); - + 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; - return 0; + goto finish; } } else - assert(!bq->blocks || (bq->write_index + (int64_t)chunk.length <= bq->blocks->index)); + 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 = pa_xnew(struct memblock_list, 1); n->chunk = chunk; pa_memblock_ref(n->chunk.memblock); n->index = bq->write_index; @@ -324,53 +403,95 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { n->prev->next = n; else bq->blocks = n; - + bq->n_blocks++; + +finish: + + delta = bq->write_index - old; + + if (delta >= (int64_t) bq->requested) { + delta -= bq->requested; + bq->requested = 0; + } else { + bq->requested -= delta; + delta = 0; + } + + bq->missing -= delta; + return 0; } -int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) { - assert(bq); - assert(chunk); +pa_bool_t pa_memblockq_prebuf_active(pa_memblockq *bq) { + pa_assert(bq); - if (bq->state == PREBUF) { + if (bq->in_prebuf) + return pa_memblockq_get_length(bq) < bq->prebuf; + else + return bq->prebuf > 0 && bq->read_index >= bq->write_index; +} + +static pa_bool_t update_prebuf(pa_memblockq *bq) { + pa_assert(bq); + + if (bq->in_prebuf) { - /* We need to pre-buffer */ if (pa_memblockq_get_length(bq) < bq->prebuf) - return -1; + return TRUE; - bq->state = RUNNING; + bq->in_prebuf = FALSE; + return FALSE; + } else { - } else if (bq->prebuf > 0 && bq->read_index >= bq->write_index) { + if (bq->prebuf > 0 && bq->read_index >= bq->write_index) { + bq->in_prebuf = TRUE; + return TRUE; + } - /* Buffer underflow protection */ - bq->state = PREBUF; - return -1; + return FALSE; } - +} + +int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) { + int64_t d; + pa_assert(bq); + pa_assert(chunk); + + /* We need to pre-buffer */ + if (update_prebuf(bq)) + return -1; + + fix_current_read(bq); + /* Do we need to spit out silence? */ - if (!bq->blocks || bq->blocks->index > bq->read_index) { + if (!bq->current_read || bq->current_read->index > bq->read_index) { size_t length; /* How much silence shall we return? */ - length = bq->blocks ? bq->blocks->index - bq->read_index : 0; + if (bq->current_read) + length = bq->current_read->index - bq->read_index; + else if (bq->write_index > bq->read_index) + length = (size_t) (bq->write_index - bq->read_index); + else + length = 0; /* We need to return silence, since no data is yet available */ - if (bq->silence) { - chunk->memblock = pa_memblock_ref(bq->silence); + if (bq->silence.memblock) { + *chunk = bq->silence; + pa_memblock_ref(chunk->memblock); + + if (length > 0 && length < chunk->length) + chunk->length = length; - 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) + if (length <= 0) return -1; - + chunk->memblock = NULL; chunk->length = length; } @@ -380,89 +501,48 @@ int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) { } /* Ok, let's pass real data to the caller */ - assert(bq->blocks->index == bq->read_index); - - *chunk = bq->blocks->chunk; + *chunk = bq->current_read->chunk; pa_memblock_ref(chunk->memblock); + pa_assert(bq->read_index >= bq->current_read->index); + d = bq->read_index - bq->current_read->index; + chunk->index += d; + chunk->length -= d; + return 0; } -void pa_memblockq_drop(pa_memblockq *bq, const pa_memchunk *chunk, size_t length) { - assert(bq); - assert(length % bq->base == 0); - - assert(!chunk || length <= chunk->length); - - if (chunk) { - - if (bq->blocks && bq->blocks->index == bq->read_index) { - /* The first item in queue is valid */ - - /* Does the chunk match with what the user supplied us? */ - if (memcmp(chunk, &bq->blocks->chunk, sizeof(pa_memchunk)) != 0) - return; - - } else { - size_t l; - - /* The first item in the queue is not yet relevant */ - - assert(!bq->blocks || bq->blocks->index > bq->read_index); - l = bq->blocks ? bq->blocks->index - bq->read_index : 0; - - if (bq->silence) { - - if (!l || l > pa_memblock_get_length(bq->silence)) - l = pa_memblock_get_length(bq->silence); - - } +void pa_memblockq_drop(pa_memblockq *bq, size_t length) { + int64_t old, delta; + pa_assert(bq); + pa_assert(length % bq->base == 0); - /* Do the entries still match? */ - if (chunk->index != 0 || chunk->length != l || chunk->memblock != bq->silence) - return; - } - } + old = bq->read_index; while (length > 0) { - if (bq->blocks) { - size_t d; + /* Do not drop any data when we are in prebuffering mode */ + if (update_prebuf(bq)) + break; - assert(bq->blocks->index >= bq->read_index); + fix_current_read(bq); - 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; - } + if (bq->current_read) { + int64_t p, d; - assert(bq->blocks->index == bq->read_index); + /* We go through this piece by piece to make sure we don't + * drop more than allowed by prebuf */ - if (bq->blocks->chunk.length <= length) { - /* We need to drop the full block */ + p = bq->current_read->index + bq->current_read->chunk.length; + pa_assert(p >= bq->read_index); + d = p - bq->read_index; - length -= bq->blocks->chunk.length; - bq->read_index += bq->blocks->chunk.length; + if (d > (int64_t) length) + d = length; - drop_block(bq, bq->blocks); - } else { - /* Only the start of this block needs to be dropped */ + bq->read_index += d; + length -= d; - 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 */ @@ -470,170 +550,368 @@ void pa_memblockq_drop(pa_memblockq *bq, const pa_memchunk *chunk, size_t length break; } } -} -int pa_memblockq_is_readable(pa_memblockq *bq) { - assert(bq); + drop_backlog(bq); - if (bq->prebuf > 0) { - size_t l = pa_memblockq_get_length(bq); - - if (bq->state == PREBUF && l < bq->prebuf) - return 0; + delta = bq->read_index - old; + bq->missing += delta; +} - if (l <= 0) - return 0; - } +void pa_memblockq_rewind(pa_memblockq *bq, size_t length) { + pa_assert(bq); + pa_assert(length % bq->base == 0); + + /* This is kind of the inverse of pa_memblockq_drop() */ - return 1; + bq->read_index -= length; + bq->missing -= length; } -int pa_memblockq_is_writable(pa_memblockq *bq, size_t length) { - assert(bq); +pa_bool_t pa_memblockq_is_readable(pa_memblockq *bq) { + pa_assert(bq); - if (length % bq->base) - return 0; - - return pa_memblockq_get_length(bq) + length <= bq->tlength; + if (pa_memblockq_prebuf_active(bq)) + return FALSE; + + if (pa_memblockq_get_length(bq) <= 0) + return FALSE; + + return TRUE; } size_t pa_memblockq_get_length(pa_memblockq *bq) { - assert(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; - assert(bq); + 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) { - assert(bq); - return bq->minreq; + return l >= bq->minreq ? l : 0; } void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) { - assert(bq); + int64_t old, delta; + pa_assert(bq); + + old = bq->write_index; switch (seek) { case PA_SEEK_RELATIVE: bq->write_index += offset; - return; + break; case PA_SEEK_ABSOLUTE: bq->write_index = offset; - return; + break; case PA_SEEK_RELATIVE_ON_READ: bq->write_index = bq->read_index + offset; - return; + 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; - return; + 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(); } - assert(0); + drop_backlog(bq); + + delta = bq->write_index - old; + + if (delta >= (int64_t) 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) { - assert(bq); - - while (bq->blocks) - drop_block(bq, bq->blocks); + int64_t old, delta; + pa_assert(bq); - assert(bq->n_blocks == 0); + pa_memblockq_silence(bq); + old = bq->write_index; bq->write_index = bq->read_index; pa_memblockq_prebuf_force(bq); + + delta = bq->write_index - old; + + if (delta >= (int64_t) 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) { - assert(bq); - + pa_assert(bq); + return bq->tlength; } +size_t pa_memblockq_get_minreq(pa_memblockq *bq) { + pa_assert(bq); + + return bq->minreq; +} + int64_t pa_memblockq_get_read_index(pa_memblockq *bq) { - assert(bq); + pa_assert(bq); + return bq->read_index; } int64_t pa_memblockq_get_write_index(pa_memblockq *bq) { - assert(bq); + pa_assert(bq); + return bq->write_index; } int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk) { pa_memchunk rchunk; - assert(bq); - assert(chunk && bq->base); - + 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) + if (r < 0) { + pa_mcalign_flush(bq->mcalign); return -1; + } } return 0; } -void pa_memblockq_shorten(pa_memblockq *bq, size_t length) { - size_t l; - assert(bq); - - l = pa_memblockq_get_length(bq); - - if (l > length) - pa_memblockq_drop(bq, NULL, l - length); -} - void pa_memblockq_prebuf_disable(pa_memblockq *bq) { - assert(bq); + pa_assert(bq); - if (bq->state == PREBUF) - bq->state = RUNNING; + bq->in_prebuf = FALSE; } void pa_memblockq_prebuf_force(pa_memblockq *bq) { - assert(bq); + pa_assert(bq); - if (bq->state == RUNNING && bq->prebuf > 0) - bq->state = PREBUF; + if (bq->prebuf > 0) + bq->in_prebuf = TRUE; } size_t pa_memblockq_get_maxlength(pa_memblockq *bq) { - assert(bq); + pa_assert(bq); return bq->maxlength; } size_t pa_memblockq_get_prebuf(pa_memblockq *bq) { - assert(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); + + if (tlength <= 0) + tlength = bq->maxlength; + + old_tlength = bq->tlength; + bq->tlength = ((tlength+bq->base-1)/bq->base)*bq->base; + + if (bq->tlength > bq->maxlength) + bq->tlength = bq->maxlength; + + if (bq->prebuf > bq->tlength) + pa_memblockq_set_prebuf(bq, bq->tlength); + + if (bq->minreq > bq->tlength) + pa_memblockq_set_minreq(bq, bq->tlength); + + bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength; +} + +void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) { + pa_assert(bq); + + if (prebuf == (size_t) -1) + prebuf = bq->tlength; + + bq->prebuf = ((prebuf+bq->base-1)/bq->base)*bq->base; + + if (prebuf > 0 && bq->prebuf < bq->base) + bq->prebuf = bq->base; + + if (bq->prebuf > bq->tlength) + bq->prebuf = bq->tlength; + + if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf) + bq->in_prebuf = FALSE; + + if (bq->minreq > bq->prebuf) + pa_memblockq_set_minreq(bq, bq->prebuf); +} + +void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { + pa_assert(bq); + + bq->minreq = (minreq/bq->base)*bq->base; + + if (bq->minreq > bq->tlength) + bq->minreq = bq->tlength; + + if (bq->minreq > bq->prebuf) + bq->minreq = bq->prebuf; + + if (bq->minreq < bq->base) + bq->minreq = bq->base; +} + +void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) { + pa_assert(bq); + + bq->maxrewind = (maxrewind/bq->base)*bq->base; +} + +int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) { + + pa_assert(bq); + pa_assert(source); + + pa_memblockq_prebuf_disable(bq); + + for (;;) { + pa_memchunk chunk; + + if (pa_memblockq_peek(source, &chunk) < 0) + return 0; + + pa_assert(chunk.length > 0); + + if (chunk.memblock) { + + if (pa_memblockq_push_align(bq, &chunk) < 0) { + pa_memblock_unref(chunk.memblock); + return -1; + } + + pa_memblock_unref(chunk.memblock); + } else + pa_memblockq_seek(bq, chunk.length, PA_SEEK_RELATIVE); + + pa_memblockq_drop(bq, chunk.length); + } +} + +void pa_memblockq_willneed(pa_memblockq *bq) { + struct list_item *q; + + pa_assert(bq); + + fix_current_read(bq); + + for (q = bq->current_read; q; q = q->next) + pa_memchunk_will_need(&q->chunk); +} + +void pa_memblockq_set_silence(pa_memblockq *bq, pa_memchunk *silence) { + pa_assert(bq); + + if (bq->silence.memblock) + pa_memblock_unref(bq->silence.memblock); + + if (silence) { + bq->silence = *silence; + pa_memblock_ref(bq->silence.memblock); + } else + pa_memchunk_reset(&bq->silence); +} + +pa_bool_t pa_memblockq_is_empty(pa_memblockq *bq) { + pa_assert(bq); + + return !bq->blocks; +} + +void pa_memblockq_silence(pa_memblockq *bq) { + pa_assert(bq); + + while (bq->blocks) + drop_block(bq, bq->blocks); + + pa_assert(bq->n_blocks == 0); +} + +unsigned pa_memblockq_get_nblocks(pa_memblockq *bq) { + pa_assert(bq); + + return bq->n_blocks; +} + +size_t pa_memblockq_get_base(pa_memblockq *bq) { + pa_assert(bq); + + return bq->base; +} diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h index 4d701a80..81f7cbb8 100644 --- a/src/pulsecore/memblockq.h +++ b/src/pulsecore/memblockq.h @@ -1,21 +1,21 @@ #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 @@ -39,37 +39,40 @@ 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 whzen reading unitialized data + + - maxrewind: how many bytes of history to keep in the queue + + - silence: return this memchunk 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 prebuf, size_t minreq, - pa_memblock *silence); + size_t maxrewind, + pa_memchunk *silence); void pa_memblockq_free(pa_memblockq*bq); @@ -81,19 +84,19 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *chunk); * 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 */ +/* 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, but only if the first - * chunk in the queue matches the one passed here. If NULL is passed, - * this check isn't done. */ -void pa_memblockq_drop(pa_memblockq *bq, const pa_memchunk *chunk, size_t length); +/* 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); - -/* Test if the pa_memblockq is currently writable for the specified amount of bytes */ -int pa_memblockq_is_writable(pa_memblockq *bq, size_t length); +pa_bool_t pa_memblockq_is_readable(pa_memblockq *bq); /* Return the length of the queue in bytes */ size_t pa_memblockq_get_length(pa_memblockq *bq); @@ -101,6 +104,13 @@ 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); + +/* Directly moves the data from the source memblockq into bq */ +int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source); + /* Returns the minimal request value */ size_t pa_memblockq_get_minreq(pa_memblockq *bq); @@ -119,10 +129,8 @@ 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); +/* Rewind the read index. If the history is shorter than the specified length we'll point to silence afterwards. */ +void pa_memblockq_rewind(pa_memblockq *bq, size_t length); /* Ignore prebuf for now */ void pa_memblockq_prebuf_disable(pa_memblockq *bq); @@ -136,4 +144,29 @@ 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. Always call in order. */ +void pa_memblockq_set_maxlength(pa_memblockq *memblockq, size_t maxlength); /* might modify tlength, prebuf, minreq too */ +void pa_memblockq_set_tlength(pa_memblockq *memblockq, size_t tlength); /* might modify minreq, too */ +void pa_memblockq_set_prebuf(pa_memblockq *memblockq, size_t prebuf); /* might modify minreq, too */ +void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq); +void pa_memblockq_set_maxrewind(pa_memblockq *memblockq, size_t rewind); /* Set the maximum history size */ +void pa_memblockq_set_silence(pa_memblockq *memblockq, pa_memchunk *silence); + +/* Call pa_memchunk_willneed() for every chunk in the queue from the current read pointer to the end */ +void pa_memblockq_willneed(pa_memblockq *bq); + +/* Check whether the memblockq is completely empty, i.e. no data + * neither left nor right of the read pointer, and hence no buffered + * data for the future nor data in the backlog. */ +pa_bool_t pa_memblockq_is_empty(pa_memblockq *bq); + +void pa_memblockq_silence(pa_memblockq *bq); + +/* Check whether we currently are in prebuf state */ +pa_bool_t pa_memblockq_prebuf_active(pa_memblockq *bq); + +unsigned pa_memblockq_get_nblocks(pa_memblockq *bq); + +size_t pa_memblockq_get_base(pa_memblockq *bq); + #endif diff --git a/src/pulsecore/memchunk.c b/src/pulsecore/memchunk.c index 55c4bfa7..0bbf8590 100644 --- a/src/pulsecore/memchunk.c +++ b/src/pulsecore/memchunk.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -25,43 +25,88 @@ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <string.h> +#include <errno.h> #include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> #include "memchunk.h" -void pa_memchunk_make_writable(pa_memchunk *c, size_t min) { +pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min) { pa_memblock *n; size_t l; void *tdata, *sdata; - - assert(c); - assert(c->memblock); - if (pa_memblock_is_read_only(c->memblock) && + 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; + return c; + + l = PA_MAX(c->length, min); - 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); + tdata = pa_memblock_acquire(n); + memcpy(tdata, (uint8_t*) sdata + c->index, c->length); - pa_memblock_release(n); + pa_memblock_release(c->memblock); + pa_memblock_release(n); + pa_memblock_unref(c->memblock); + c->memblock = n; c->index = 0; + + return c; +} + +pa_memchunk* pa_memchunk_reset(pa_memchunk *c) { + pa_assert(c); + + memset(c, 0, sizeof(*c)); + + return c; +} + +pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c) { + void *p; + + pa_assert(c); + pa_assert(c->memblock); + + /* A version of pa_memblock_will_need() that works on memchunks + * instead of memblocks */ + + p = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index; + pa_will_need(p, c->length); + pa_memblock_release(c->memblock); + + return (pa_memchunk*) c; } -void pa_memchunk_reset(pa_memchunk *c) { - assert(c); +pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src) { + void *p, *q; + + pa_assert(dst); + pa_assert(src); + pa_assert(dst->length == src->length); + + p = pa_memblock_acquire(dst->memblock); + q = pa_memblock_acquire(src->memblock); + + memmove((uint8_t*) p + dst->index, + (uint8_t*) q + src->index, + dst->length); + + pa_memblock_release(dst->memblock); + pa_memblock_release(src->memblock); - c->memblock = NULL; - c->length = c->index = 0; + return dst; } diff --git a/src/pulsecore/memchunk.h b/src/pulsecore/memchunk.h index b8ce6249..9458f4ff 100644 --- a/src/pulsecore/memchunk.h +++ b/src/pulsecore/memchunk.h @@ -1,21 +1,21 @@ #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 @@ -35,11 +35,19 @@ typedef struct 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. */ -void pa_memchunk_make_writable(pa_memchunk *c, size_t min); + * 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. */ -void pa_memchunk_reset(pa_memchunk *c); +pa_memchunk* pa_memchunk_reset(pa_memchunk *c); + +/* Map a memory chunk back into memory if it was swapped out */ +pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c); + +/* Copy the data in the src memchunk to the dst memchunk */ +pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src); #endif diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c index 13a48785..5f5902c9 100644 --- a/src/pulsecore/modargs.c +++ b/src/pulsecore/modargs.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,7 +24,6 @@ #endif #include <ctype.h> -#include <assert.h> #include <stdlib.h> #include <string.h> @@ -37,6 +36,7 @@ #include <pulsecore/sink.h> #include <pulsecore/source.h> #include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "modargs.h" @@ -46,7 +46,16 @@ struct entry { static int add_key_value(pa_hashmap *map, char *key, char *value, const char* const valid_keys[]) { struct entry *e; - assert(map && key && value); + + pa_assert(map); + pa_assert(key); + pa_assert(value); + + if (pa_hashmap_get(map, key)) { + pa_xfree(key); + pa_xfree(value); + return -1; + } if (valid_keys) { const char*const* v; @@ -60,11 +69,12 @@ static int add_key_value(pa_hashmap *map, char *key, char *value, const char* co return -1; } } - - e = pa_xmalloc(sizeof(struct entry)); + + e = pa_xnew(struct entry, 1); e->key = key; e->value = value; pa_hashmap_put(map, key, e); + return 0; } @@ -72,13 +82,20 @@ 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); - assert(map); if (args) { - enum { WHITESPACE, KEY, VALUE_START, VALUE_SIMPLE, VALUE_DOUBLE_QUOTES, VALUE_TICKS } state; + 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++) { @@ -95,6 +112,8 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { case KEY: if (*p == '=') state = VALUE_START; + else if (isspace(*p)) + goto fail; else key_len++; break; @@ -160,14 +179,14 @@ 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; - assert(e); + pa_assert(e); + pa_xfree(e->key); pa_xfree(e->value); pa_xfree(e); @@ -190,7 +209,10 @@ const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *de int pa_modargs_get_value_u32(pa_modargs *ma, const char *key, uint32_t *value) { const char *v; - assert(ma && key && value); + + pa_assert(ma); + pa_assert(key); + pa_assert(value); if (!(v = pa_modargs_get_value(ma, key, NULL))) return 0; @@ -203,21 +225,27 @@ 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) { const char *v; - assert(ma && key && value); + + 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, int *value) { +int pa_modargs_get_value_boolean(pa_modargs *ma, const char *key, pa_bool_t *value) { const char *v; int r; - assert(ma && key && value); + + pa_assert(ma); + pa_assert(key); + pa_assert(value); if (!(v = pa_modargs_get_value(ma, key, NULL))) return 0; @@ -236,10 +264,10 @@ int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *rss) { const char *format; uint32_t channels; pa_sample_spec ss; - assert(ma && rss); -/* DEBUG_TRAP;*/ - + pa_assert(ma); + pa_assert(rss); + ss = *rss; if ((pa_modargs_get_value_u32(ma, "rate", &ss.rate)) < 0) return -1; @@ -257,20 +285,20 @@ int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *rss) { return -1; *rss = ss; - + return 0; } -int pa_modargs_get_channel_map(pa_modargs *ma, pa_channel_map *rmap) { +int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *rmap) { pa_channel_map map; const char *cm; - - assert(ma); - assert(rmap); + + pa_assert(ma); + pa_assert(rmap); map = *rmap; - if ((cm = pa_modargs_get_value(ma, "channel_map", NULL))) + if ((cm = pa_modargs_get_value(ma, name ? name : "channel_map", NULL))) if (!pa_channel_map_parse(&map, cm)) return -1; @@ -284,10 +312,10 @@ int pa_modargs_get_channel_map(pa_modargs *ma, pa_channel_map *rmap) { 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; - - assert(ma); - assert(rss); - assert(rmap); + + pa_assert(ma); + pa_assert(rss); + pa_assert(rmap); ss = *rss; @@ -297,7 +325,7 @@ int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *r if (!pa_channel_map_init_auto(&map, ss.channels, def)) map.channels = 0; - if (pa_modargs_get_channel_map(ma, &map) < 0) + if (pa_modargs_get_channel_map(ma, NULL, &map) < 0) return -1; if (map.channels != ss.channels) diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h index 730cf396..23766cfc 100644 --- a/src/pulsecore/modargs.h +++ b/src/pulsecore/modargs.h @@ -1,21 +1,21 @@ #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 @@ -26,6 +26,7 @@ #include <pulse/sample.h> #include <pulse/channelmap.h> #include <pulsecore/core.h> +#include <pulsecore/macro.h> typedef struct pa_modargs pa_modargs; @@ -42,13 +43,13 @@ const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *de /* 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, int *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" */ -int pa_modargs_get_channel_map(pa_modargs *ma, pa_channel_map *map); +/* 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 diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c index 00720113..ac4ca88a 100644 --- a/src/pulsecore/modinfo.c +++ b/src/pulsecore/modinfo.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,12 +24,13 @@ #endif #include <ltdl.h> -#include <assert.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" @@ -37,54 +38,55 @@ #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" -/* lt_dlsym() violates ISO C, so confide the breakage into this function to - * avoid warnings. */ -typedef void (*fnptr)(void); -static inline fnptr lt_dlsym_fn(lt_dlhandle handle, const char *symbol) { - return (fnptr) (long) lt_dlsym(handle, symbol); -} - -pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl) { +pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) { pa_modinfo *i; const char* (*func)(void); - assert(dl); + pa_bool_t (*func2) (void); + + pa_assert(dl); i = pa_xnew0(pa_modinfo, 1); - if ((func = (const char* (*)(void)) lt_dlsym_fn(dl, PA_SYMBOL_AUTHOR))) + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_AUTHOR))) i->author = pa_xstrdup(func()); - if ((func = (const char* (*)(void)) lt_dlsym_fn(dl, PA_SYMBOL_DESCRIPTION))) + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DESCRIPTION))) i->description = pa_xstrdup(func()); - if ((func = (const char* (*)(void)) lt_dlsym_fn(dl, PA_SYMBOL_USAGE))) + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_USAGE))) i->usage = pa_xstrdup(func()); - if ((func = (const char* (*)(void)) lt_dlsym_fn(dl, PA_SYMBOL_VERSION))) + 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; - assert(name); + + 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); + i = pa_modinfo_get_by_handle(dl, name); lt_dlclose(dl); return i; } void pa_modinfo_free(pa_modinfo *i) { - assert(i); + pa_assert(i); + pa_xfree(i->author); pa_xfree(i->description); pa_xfree(i->usage); diff --git a/src/pulsecore/modinfo.h b/src/pulsecore/modinfo.h index 90404504..605637c4 100644 --- a/src/pulsecore/modinfo.h +++ b/src/pulsecore/modinfo.h @@ -1,21 +1,21 @@ #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 @@ -23,16 +23,18 @@ ***/ /* 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); +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); diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c index ea3d726e..f1eeb762 100644 --- a/src/pulsecore/module.c +++ b/src/pulsecore/module.c @@ -1,18 +1,19 @@ -/* $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 @@ -26,7 +27,6 @@ #include <limits.h> #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <ctype.h> @@ -37,103 +37,80 @@ #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 -/* lt_dlsym() violates ISO C, so confide the breakage into this function to - * avoid warnings. */ -typedef void (*fnptr)(void); -static inline fnptr lt_dlsym_fn(lt_dlhandle handle, const char *symbol) { - return (fnptr) (long) lt_dlsym(handle, symbol); -} - 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; + pa_core *c = PA_CORE(userdata); struct timeval ntv; - assert(c && c->mainloop == m && c->module_auto_unload_event == e); + + 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); - ntv.tv_sec += UNLOAD_POLL_TIME; + pa_timeval_add(&ntv, UNLOAD_POLL_TIME*1000000); m->time_restart(e, &ntv); } -static inline fnptr load_sym(lt_dlhandle handle, const char *module, const char *symbol) { - char *buffer, *ch; - size_t buflen; - fnptr res; - - res = lt_dlsym_fn(handle, symbol); - if (res) - return res; - - /* As the .la files might have been cleansed from the system, we should - * try with the ltdl prefix as well. */ - - buflen = strlen(symbol) + strlen(module) + strlen("_LTX_") + 1; - buffer = pa_xmalloc(buflen); - assert(buffer); - - strcpy(buffer, module); - - for (ch = buffer;*ch != '\0';ch++) { - if (!isalnum(*ch)) - *ch = '_'; - } - - strcat(buffer, "_LTX_"); - strcat(buffer, symbol); - - res = lt_dlsym_fn(handle, buffer); - - pa_xfree(buffer); - - return res; -} - pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { pa_module *m = NULL; - int r; - - assert(c && name); + pa_bool_t (*load_once)(void); + + pa_assert(c); + pa_assert(name); if (c->disallow_module_loading) goto fail; - - m = pa_xmalloc(sizeof(pa_module)); + 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 (!(m->init = (int (*)(pa_core *_c, pa_module*_m)) load_sym(m->dl, name, PA_SYMBOL_INIT))) { - pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name); - 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->done = (void (*)(pa_core *_c, pa_module*_m)) load_sym(m->dl, name, PA_SYMBOL_DONE))) { - pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_DONE"\" not found.", name); + 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; + m->auto_unload = FALSE; + m->unload_requested = FALSE; - assert(m->init); - if (m->init(c, m) < 0) { + if (m->init(m) < 0) { pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : ""); goto fail; } @@ -141,30 +118,28 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { if (!c->modules) c->modules = pa_idxset_new(NULL, NULL); - if (!c->module_auto_unload_event) { + if (m->auto_unload && !c->module_auto_unload_event) { struct timeval ntv; pa_gettimeofday(&ntv); - ntv.tv_sec += UNLOAD_POLL_TIME; + pa_timeval_add(&ntv, UNLOAD_POLL_TIME*1000000); c->module_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c); } - assert(c->module_auto_unload_event); - - assert(c->modules); - r = pa_idxset_put(c->modules, m, &m->index); - assert(r >= 0 && m->index != PA_IDXSET_INVALID); - pa_log_info("Loaded \"%s\" (index: #%u; argument: \"%s\").", m->name, m->index, m->argument ? m->argument : ""); + 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); @@ -175,30 +150,33 @@ fail: } static void pa_module_free(pa_module *m) { - assert(m && m->done && m->core); + 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); + pa_log_info("Unloading \"%s\" (index: #%u).", m->name, m->index); - m->done(m->core, m); + if (m->done) + m->done(m); lt_dlclose(m->dl); - - pa_log_info("Unloaded \"%s\" (index: #%u).", m->name, m->index); + + 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) { - assert(c && m); + pa_assert(c); + pa_assert(m); - assert(c->modules); + pa_assert(c->modules); if (!(m = pa_idxset_remove_by_data(c->modules, m, NULL))) return; @@ -207,9 +185,9 @@ void pa_module_unload(pa_core *c, pa_module *m) { void pa_module_unload_by_index(pa_core *c, uint32_t idx) { pa_module *m; - assert(c && idx != PA_IDXSET_INVALID); + pa_assert(c); + pa_assert(idx != PA_IDXSET_INVALID); - assert(c->modules); if (!(m = pa_idxset_remove_by_index(c->modules, idx))) return; @@ -218,13 +196,14 @@ void pa_module_unload_by_index(pa_core *c, uint32_t idx) { static void free_callback(void *p, PA_GCC_UNUSED void *userdata) { pa_module *m = p; - assert(m); + pa_assert(m); pa_module_free(m); } void pa_module_unload_all(pa_core *c) { pa_module *m; - assert(c); + + pa_assert(c); if (!c->modules) return; @@ -249,8 +228,11 @@ void pa_module_unload_all(pa_core *c) { static int unused_callback(void *p, PA_GCC_UNUSED uint32_t idx, int *del, void *userdata) { pa_module *m = p; time_t *now = userdata; - assert(p && del && now); - + + 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; @@ -261,18 +243,18 @@ static int unused_callback(void *p, PA_GCC_UNUSED uint32_t idx, int *del, void * void pa_module_unload_unused(pa_core *c) { time_t now; - assert(c); + 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; - assert(m); + pa_assert(m); if (m->unload_requested) { pa_module_free(m); @@ -283,20 +265,21 @@ static int unload_callback(void *p, PA_GCC_UNUSED uint32_t idx, int *del, PA_GCC } static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) { - pa_core *core = 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) { - assert(m); + pa_assert(m); - m->unload_requested = 1; + m->unload_requested = TRUE; 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); @@ -305,19 +288,19 @@ void pa_module_unload_request(pa_module *m) { } void pa_module_set_used(pa_module*m, int used) { - assert(m); + pa_assert(m); if (m->n_used != used) pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index); - - if (m->n_used != used && used == 0) + + 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) { - assert(m); + pa_assert(m); - return pa_modinfo_get_by_handle(m->dl); + return pa_modinfo_get_by_handle(m->dl, m->name); } diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h index 8c320be8..ec582f25 100644 --- a/src/pulsecore/module.h +++ b/src/pulsecore/module.h @@ -1,21 +1,21 @@ #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 @@ -36,17 +36,17 @@ struct pa_module { uint32_t index; lt_dlhandle dl; - - int (*init)(pa_core *c, pa_module*m); - void (*done)(pa_core *c, pa_module*m); + + int (*init)(pa_module*m); + void (*done)(pa_module*m); void *userdata; int n_used; - int auto_unload; + pa_bool_t auto_unload; time_t last_used_time; - int unload_requested; + pa_bool_t unload_requested; }; pa_module* pa_module_load(pa_core *c, const char *name, const char*argument); @@ -60,10 +60,25 @@ 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; } -#define PA_MODULE_DESCRIPTION(s) const char * pa__get_description(void) { return s; } -#define PA_MODULE_USAGE(s) const char * pa__get_usage(void) { return s; } -#define PA_MODULE_VERSION(s) const char * pa__get_version(void) { return s; } +#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); diff --git a/src/pulsecore/msgobject.c b/src/pulsecore/msgobject.c new file mode 100644 index 00000000..81417ea4 --- /dev/null +++ b/src/pulsecore/msgobject.c @@ -0,0 +1,47 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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..1a43fa35 --- /dev/null +++ b/src/pulsecore/msgobject.h @@ -0,0 +1,52 @@ +#ifndef foopulsemsgobjecthfoo +#define foopulsemsgobjecthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General 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 index 094d637d..35465b7b 100644 --- a/src/pulsecore/mutex-posix.c +++ b/src/pulsecore/mutex-posix.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,20 +23,16 @@ #include <config.h> #endif -#include <assert.h> #include <pthread.h> - -#include <atomic_ops.h> +#include <errno.h> #include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> #include "mutex.h" -#define ASSERT_SUCCESS(x) do { \ - int _r = (x); \ - assert(_r == 0); \ -} while(0) - struct pa_mutex { pthread_mutex_t mutex; }; @@ -45,68 +41,100 @@ struct pa_cond { pthread_cond_t cond; }; -pa_mutex* pa_mutex_new(int recursive) { +pa_mutex* pa_mutex_new(pa_bool_t recursive, pa_bool_t inherit_priority) { pa_mutex *m; pthread_mutexattr_t attr; + int r; - pthread_mutexattr_init(&attr); + pa_assert_se(pthread_mutexattr_init(&attr) == 0); if (recursive) - ASSERT_SUCCESS(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_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); - ASSERT_SUCCESS(pthread_mutex_init(&m->mutex, &attr)); +#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) { - assert(m); + pa_assert(m); - ASSERT_SUCCESS(pthread_mutex_destroy(&m->mutex)); + pa_assert_se(pthread_mutex_destroy(&m->mutex) == 0); pa_xfree(m); } void pa_mutex_lock(pa_mutex *m) { - assert(m); + pa_assert(m); - ASSERT_SUCCESS(pthread_mutex_lock(&m->mutex)); + 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) { - assert(m); + pa_assert(m); - ASSERT_SUCCESS(pthread_mutex_unlock(&m->mutex)); + pa_assert_se(pthread_mutex_unlock(&m->mutex) == 0); } pa_cond *pa_cond_new(void) { pa_cond *c; c = pa_xnew(pa_cond, 1); - - ASSERT_SUCCESS(pthread_cond_init(&c->cond, NULL)); + pa_assert_se(pthread_cond_init(&c->cond, NULL) == 0); return c; } void pa_cond_free(pa_cond *c) { - assert(c); + pa_assert(c); - ASSERT_SUCCESS(pthread_cond_destroy(&c->cond)); + pa_assert_se(pthread_cond_destroy(&c->cond) == 0); pa_xfree(c); } void pa_cond_signal(pa_cond *c, int broadcast) { - assert(c); + pa_assert(c); if (broadcast) - ASSERT_SUCCESS(pthread_cond_broadcast(&c->cond)); + pa_assert_se(pthread_cond_broadcast(&c->cond) == 0); else - ASSERT_SUCCESS(pthread_cond_signal(&c->cond)); + pa_assert_se(pthread_cond_signal(&c->cond) == 0); } int pa_cond_wait(pa_cond *c, pa_mutex *m) { - assert(c); - assert(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 index 3710d914..5e884e7f 100644 --- a/src/pulsecore/mutex-win32.c +++ b/src/pulsecore/mutex-win32.c @@ -1,18 +1,18 @@ -/* $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 @@ -38,7 +38,7 @@ struct pa_cond { pa_hashmap *wait_events; }; -pa_mutex* pa_mutex_new(int recursive) { +pa_mutex* pa_mutex_new(pa_bool_t recursive, pa_bool_t inherit_priority) { pa_mutex *m; m = pa_xnew(pa_mutex, 1); diff --git a/src/pulsecore/mutex.h b/src/pulsecore/mutex.h index b3b9c5c6..36e1d635 100644 --- a/src/pulsecore/mutex.h +++ b/src/pulsecore/mutex.h @@ -1,32 +1,41 @@ #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; -pa_mutex* pa_mutex_new(int recursive); +/* 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; diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c index fcd271bf..cc18adab 100644 --- a/src/pulsecore/namereg.c +++ b/src/pulsecore/namereg.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -25,7 +25,6 @@ #include <stdlib.h> #include <string.h> -#include <assert.h> #include <string.h> #include <stdio.h> @@ -36,6 +35,7 @@ #include <pulsecore/sink.h> #include <pulsecore/core-subscribe.h> #include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "namereg.h" @@ -54,20 +54,20 @@ static int is_valid_char(char c) { c == '_'; } -static int is_valid_name(const char *name) { +pa_bool_t pa_namereg_is_valid_name(const char *name) { const char *c; if (*name == 0) - return 0; - + return FALSE; + for (c = name; *c && (c-name < PA_NAME_MAX); c++) if (!is_valid_char(*c)) - return 0; + return FALSE; if (*c) - return 0; - - return 1; + return FALSE; + + return TRUE; } static char* cleanup_name(const char *name) { @@ -78,7 +78,7 @@ static char* cleanup_name(const char *name) { 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 : '_'; @@ -88,30 +88,29 @@ static char* cleanup_name(const char *name) { } void pa_namereg_free(pa_core *c) { - assert(c); - + pa_assert(c); + if (!c->namereg) return; - - assert(pa_hashmap_size(c->namereg) == 0); + + 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; - int r; - - assert(c); - assert(name); - assert(data); + + pa_assert(c); + pa_assert(name); + pa_assert(data); if (!*name) return NULL; - + if ((type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE) && - !is_valid_name(name) ) { - + !pa_namereg_is_valid_name(name) ) { + if (fail) return NULL; @@ -136,11 +135,11 @@ const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t pa_xfree(n); return NULL; } - + k = pa_xnew(char, l+4); - + for (i = 2; i <= 99; i++) { - snprintf(k, l+4, "%s.%u", name, i); + pa_snprintf(k, l+4, "%s.%u", name, i); if (!(e = pa_hashmap_get(c->namereg, k))) break; @@ -151,59 +150,57 @@ const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t 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; - r = pa_hashmap_put(c->namereg, e->name, e); - assert (r >= 0); + 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; - - assert(c); - assert(name); - e = pa_hashmap_remove(c->namereg, name); - assert(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) { +void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, pa_bool_t autoload) { struct namereg_entry *e; uint32_t idx; - assert(c); - + 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; } @@ -212,7 +209,7 @@ void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, int a if (!name) return NULL; - + if (c->namereg && (e = pa_hashmap_get(c->namereg, name))) if (e->type == type) return e->data; @@ -221,12 +218,12 @@ void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, int a 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; } @@ -242,9 +239,9 @@ void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, int a int pa_namereg_set_default(pa_core*c, const char *name, pa_namereg_type_t type) { char **s; - - assert(c); - assert(type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE); + + 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; @@ -254,9 +251,9 @@ int pa_namereg_set_default(pa_core*c, const char *name, pa_namereg_type_t type) if (name && *s && !strcmp(name, *s)) return 0; - if (!is_valid_name(name)) + 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); @@ -266,12 +263,12 @@ 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) { pa_sink *s; - - assert(c); + + 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); @@ -281,8 +278,8 @@ const char *pa_namereg_get_default_sink_name(pa_core *c) { const char *pa_namereg_get_default_source_name(pa_core *c) { pa_source *s; uint32_t idx; - - assert(c); + + pa_assert(c); if (c->default_source_name) return c->default_source_name; diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h index 53fb6618..af0153ec 100644 --- a/src/pulsecore/namereg.h +++ b/src/pulsecore/namereg.h @@ -1,21 +1,21 @@ #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 @@ -23,6 +23,7 @@ ***/ #include <pulsecore/core.h> +#include <pulsecore/macro.h> #define PA_NAME_MAX 128 @@ -36,10 +37,12 @@ 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); +void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, pa_bool_t 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 index 785289eb..809d6c75 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -1,21 +1,22 @@ #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 @@ -33,10 +34,10 @@ enum { PA_COMMAND_TIMEOUT, /* pseudo command */ PA_COMMAND_REPLY, - /* Commands from client to server */ - PA_COMMAND_CREATE_PLAYBACK_STREAM, + /* 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, + 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, @@ -51,7 +52,7 @@ enum { 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, @@ -61,46 +62,49 @@ enum { PA_COMMAND_GET_MODULE_INFO_LIST, PA_COMMAND_GET_CLIENT_INFO, PA_COMMAND_GET_CLIENT_INFO_LIST, - PA_COMMAND_GET_SINK_INPUT_INFO, - PA_COMMAND_GET_SINK_INPUT_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, - /* Commands from server to client */ + /* SERVER->CLIENT */ PA_COMMAND_REQUEST, PA_COMMAND_OVERFLOW, PA_COMMAND_UNDERFLOW, @@ -109,9 +113,41 @@ enum { PA_COMMAND_SUBSCRIBE_EVENT, /* A few more client->server commands */ + + /* Supported since protocol v10 (0.9.5) */ PA_COMMAND_MOVE_SINK_INPUT, PA_COMMAND_MOVE_SOURCE_OUTPUT, + /* Supported since protocol v11 (0.9.7) */ + PA_COMMAND_SET_SINK_INPUT_MUTE, + + PA_COMMAND_SUSPEND_SINK, + PA_COMMAND_SUSPEND_SOURCE, + + /* Supported since protocol v12 (0.9.8) */ + PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR, + PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR, + + PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE, + PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE, + + /* SERVER->CLIENT */ + PA_COMMAND_PLAYBACK_STREAM_SUSPENDED, + PA_COMMAND_RECORD_STREAM_SUSPENDED, + PA_COMMAND_PLAYBACK_STREAM_MOVED, + PA_COMMAND_RECORD_STREAM_MOVED, + + /* Supported since protocol v13 (0.9.10) */ + PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST, + PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST, + PA_COMMAND_UPDATE_CLIENT_PROPLIST, + PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST, + PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST, + PA_COMMAND_REMOVE_CLIENT_PROPLIST, + + /* SERVER->CLIENT */ + PA_COMMAND_STARTED, + PA_COMMAND_MAX }; diff --git a/src/pulsecore/object.c b/src/pulsecore/object.c new file mode 100644 index 00000000..9a2f28f3 --- /dev/null +++ b/src/pulsecore/object.c @@ -0,0 +1,70 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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..7dcfa2eb --- /dev/null +++ b/src/pulsecore/object.h @@ -0,0 +1,104 @@ +#ifndef foopulseobjecthfoo +#define foopulseobjecthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General 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-posix.c b/src/pulsecore/once-posix.c deleted file mode 100644 index 865997df..00000000 --- a/src/pulsecore/once-posix.c +++ /dev/null @@ -1,69 +0,0 @@ -/* $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 <pthread.h> -#include <assert.h> - -#include <pulsecore/mutex.h> - -#include "once.h" - -#define ASSERT_SUCCESS(x) do { \ - int _r = (x); \ - assert(_r == 0); \ -} while(0) - -static pa_mutex *global_mutex; -static pthread_once_t global_mutex_once = PTHREAD_ONCE_INIT; - -static void global_mutex_once_func(void) { - global_mutex = pa_mutex_new(0); -} - -void pa_once(pa_once_t *control, pa_once_func_t func) { - assert(control); - assert(func); - - /* Create the global mutex */ - ASSERT_SUCCESS(pthread_once(&global_mutex_once, global_mutex_once_func)); - - /* Create the local mutex */ - pa_mutex_lock(global_mutex); - if (!control->mutex) - control->mutex = pa_mutex_new(1); - pa_mutex_unlock(global_mutex); - - /* Execute function */ - pa_mutex_lock(control->mutex); - if (!control->once_value) { - control->once_value = 1; - func(); - } - pa_mutex_unlock(control->mutex); - - /* 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. */ -} diff --git a/src/pulsecore/once-win32.c b/src/pulsecore/once-win32.c deleted file mode 100644 index 8b9282f4..00000000 --- a/src/pulsecore/once-win32.c +++ /dev/null @@ -1,67 +0,0 @@ -/* $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 <stdio.h> - -#include <windows.h> - -#include <pulsecore/mutex.h> - -#include "once.h" - -void pa_once(pa_once_t *control, pa_once_func_t func) { - HANDLE mutex; - char name[64]; - - assert(control); - assert(func); - - /* Create the global mutex */ - sprintf(name, "pulse%d", (int)GetCurrentProcessId()); - - mutex = CreateMutex(NULL, FALSE, name); - assert(mutex); - - /* Create the local mutex */ - WaitForSingleObject(mutex, INFINITE); - if (!control->mutex) - control->mutex = pa_mutex_new(1); - ReleaseMutex(mutex); - - CloseHandle(mutex); - - /* Execute function */ - pa_mutex_lock(control->mutex); - if (!control->once_value) { - control->once_value = 1; - func(); - } - pa_mutex_unlock(control->mutex); - - /* 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. */ -} diff --git a/src/pulsecore/once.c b/src/pulsecore/once.c new file mode 100644 index 00000000..989741dc --- /dev/null +++ b/src/pulsecore/once.c @@ -0,0 +1,94 @@ +/*** + 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 index 0aabb3f2..576d40fa 100644 --- a/src/pulsecore/once.h +++ b/src/pulsecore/once.h @@ -1,21 +1,21 @@ #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 @@ -23,16 +23,52 @@ ***/ #include <pulsecore/mutex.h> +#include <pulsecore/atomic.h> typedef struct pa_once { - unsigned int once_value; - pa_mutex *mutex; -} pa_once_t; + pa_atomic_ptr_t mutex; + pa_atomic_t ref, done; +} pa_once; -#define PA_ONCE_INIT { .once_value = 0, .mutex = NULL } +#define PA_ONCE_INIT \ + { \ + .mutex = PA_ATOMIC_PTR_INIT(NULL), \ + .ref = PA_ATOMIC_INIT(0), \ + .done = PA_ATOMIC_INIT(0) \ + } -typedef void (*pa_once_func_t) (void); +/* 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) -void pa_once(pa_once_t *o, pa_once_func_t f); +/* + + 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 index 8b010f01..cee468bd 100644 --- a/src/pulsecore/packet.c +++ b/src/pulsecore/packet.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -23,55 +23,55 @@ #include <config.h> #endif -#include <assert.h> #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; - assert(length); + pa_assert(length > 0); - p = pa_xmalloc(sizeof(pa_packet)+length); - p->ref = 1; + p = pa_xmalloc(PA_ALIGN(sizeof(pa_packet)) + length); + PA_REFCNT_INIT(p); p->length = length; - p->data = (uint8_t*) (p+1); + 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; - assert(data); - assert(length); + pa_assert(data); + pa_assert(length > 0); p = pa_xnew(pa_packet, 1); - p->ref = 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) { - assert(p); - assert(p->ref >= 1); - - p->ref++; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); return p; } void pa_packet_unref(pa_packet *p) { - assert(p); - assert(p->ref >= 1); - - if (--p->ref == 0) { + 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 index 7842857a..5989b1fa 100644 --- a/src/pulsecore/packet.h +++ b/src/pulsecore/packet.h @@ -1,21 +1,21 @@ #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 @@ -25,9 +25,11 @@ #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; - unsigned ref; size_t length; uint8_t *data; } pa_packet; diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c index da1647af..f2b6b2cf 100644 --- a/src/pulsecore/parseaddr.c +++ b/src/pulsecore/parseaddr.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -24,13 +24,13 @@ #endif #include <string.h> -#include <assert.h> #include <stdlib.h> #include <pulse/xmalloc.h> - #include <pulse/util.h> + #include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "parseaddr.h" @@ -43,7 +43,9 @@ * 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) { - assert(s && ret_port); + pa_assert(s); + pa_assert(ret_port); + if (*s == '[') { char *e; if (!(e = strchr(s+1, ']'))) @@ -53,11 +55,11 @@ static char *parse_host(const char *s, uint16_t *ret_port) { *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); @@ -68,37 +70,43 @@ static char *parse_host(const char *s, uint16_t *ret_port) { int pa_parse_address(const char *name, pa_parsed_address *ret_p) { const char *p; - assert(name && ret_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:") || pa_startswith(p, "tcp4:")) { + } 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; @@ -109,7 +117,6 @@ int pa_parse_address(const char *name, pa_parsed_address *ret_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 index 0393f665..5fbcb9a7 100644 --- a/src/pulsecore/parseaddr.h +++ b/src/pulsecore/parseaddr.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index 6ecf710a..e6a6ae4d 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,7 +26,6 @@ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> @@ -34,6 +34,9 @@ #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" @@ -94,6 +97,8 @@ static const char *command_names[PA_COMMAND_MAX] = { #endif +PA_STATIC_FLIST_DECLARE(reply_infos, 0, pa_xfree); + struct reply_info { pa_pdispatch *pdispatch; PA_LLIST_FIELDS(struct reply_info); @@ -105,7 +110,7 @@ struct reply_info { }; struct pa_pdispatch { - int ref; + PA_REFCNT_DECLARE; pa_mainloop_api *mainloop; const pa_pdispatch_cb_t *callback_table; unsigned n_commands; @@ -116,24 +121,27 @@ struct pa_pdispatch { }; static void reply_info_free(struct reply_info *r) { - assert(r && r->pdispatch && r->pdispatch->mainloop); + 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); - - pa_xfree(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; - assert(mainloop); + pa_assert(mainloop); + + pa_assert((entries && table) || (!entries && !table)); - assert((entries && table) || (!entries && !table)); - - pd = pa_xmalloc(sizeof(pa_pdispatch)); - pd->ref = 1; + pd = pa_xnew(pa_pdispatch, 1); + PA_REFCNT_INIT(pd); pd->mainloop = mainloop; pd->callback_table = table; pd->n_commands = entries; @@ -141,12 +149,12 @@ pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *mainloop, const pa_pdispatch_cb_ pd->drain_callback = NULL; pd->drain_userdata = NULL; pd->creds = NULL; - + return pd; } static void pdispatch_free(pa_pdispatch *pd) { - assert(pd); + pa_assert(pd); while (pd->replies) { if (pd->replies->free_cb) @@ -154,7 +162,7 @@ static void pdispatch_free(pa_pdispatch *pd) { reply_info_free(pd->replies); } - + pa_xfree(pd); } @@ -162,16 +170,16 @@ static void run_action(pa_pdispatch *pd, struct reply_info *r, uint32_t command, pa_pdispatch_cb_t callback; void *userdata; uint32_t tag; - assert(r); + 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)) @@ -184,27 +192,31 @@ int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*packet, const pa_creds *creds, uint32_t tag, command; pa_tagstruct *ts = NULL; int ret = -1; - assert(pd && packet && packet->data); + + 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); - assert(ts); - + 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])) - snprintf((char*) (p = t), sizeof(t), "%u", command); - + pa_snprintf((char*) (p = t), sizeof(t), "%u", command); + pa_log("Recieved opcode <%s>", p); } #endif @@ -231,10 +243,10 @@ int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*packet, const pa_creds *creds, } ret = 0; - + finish: pd->creds = NULL; - + if (ts) pa_tagstruct_free(ts); @@ -245,7 +257,12 @@ finish: 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; - assert(r && r->time_event == e && r->pdispatch && r->pdispatch->mainloop == m && r->callback); + + 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); } @@ -253,33 +270,39 @@ static void timeout_callback(pa_mainloop_api*m, pa_time_event*e, PA_GCC_UNUSED c 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; - assert(pd && pd->ref >= 1 && cb); - r = pa_xmalloc(sizeof(struct reply_info)); + 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; - r->time_event = pd->mainloop->time_new(pd->mainloop, &tv, timeout_callback, r); - assert(r->time_event); + 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) { - assert(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) { - assert(pd); - assert(!cb || pa_pdispatch_is_pending(pd)); + 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; @@ -287,32 +310,37 @@ void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, void (*cb)(pa_pdispatch * void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata) { struct reply_info *r, *n; - assert(pd); + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); for (r = pd->replies; r; r = n) { n = r->next; - if (r->userdata == userdata) + if (r->userdata == userdata) reply_info_free(r); } } void pa_pdispatch_unref(pa_pdispatch *pd) { - assert(pd && pd->ref >= 1); + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); - if (!(--(pd->ref))) + if (PA_REFCNT_DEC(pd) <= 0) pdispatch_free(pd); } pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd) { - assert(pd && pd->ref >= 1); - pd->ref++; + 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) { - assert(pd); - assert(pd->ref >= 1); - + 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 index 479eb6b4..5c31d80e 100644 --- a/src/pulsecore/pdispatch.h +++ b/src/pulsecore/pdispatch.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c index 6e0c085e..addb17cc 100644 --- a/src/pulsecore/pid.c +++ b/src/pulsecore/pid.c @@ -1,18 +1,19 @@ -/* $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 @@ -30,7 +31,6 @@ #include <sys/stat.h> #include <string.h> #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <limits.h> #include <signal.h> @@ -40,10 +40,12 @@ #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" @@ -54,23 +56,23 @@ static pid_t read_pid(const char *fn, int fd) { char t[20], *e; uint32_t pid; - assert(fn && fd >= 0); + pa_assert(fn); + pa_assert(fd >= 0); if ((r = pa_loop_read(fd, t, sizeof(t)-1, NULL)) < 0) { - pa_log_warn("WARNING: failed to read PID file '%s': %s", - fn, pa_cstrerror(errno)); + 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("WARNING: failed to parse PID file '%s'", fn); + pa_log_warn("Failed to parse PID file '%s'", fn); return (pid_t) -1; } @@ -79,24 +81,32 @@ static pid_t read_pid(const char *fn, int fd) { 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, S_IRUSR|S_IWUSR)) < 0) { + + 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("WARNING: failed to open PID file '%s': %s", - fn, pa_cstrerror(errno)); + 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("WARNING: failed to fstat() PID file '%s': %s", - fn, pa_cstrerror(errno)); + pa_log_warn("Failed to fstat() PID file '%s': %s", fn, pa_cstrerror(errno)); goto fail; } @@ -107,9 +117,9 @@ static int open_pid_file(const char *fn, int mode) { if (pa_lock_fd(fd, 0) < 0) goto fail; - if (close(fd) < 0) { - pa_log_warn("WARNING: failed to close file '%s': %s", - fn, pa_cstrerror(errno)); + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno)); + fd = -1; goto fail; } @@ -122,84 +132,143 @@ fail: if (fd >= 0) { pa_lock_fd(fd, 0); - close(fd); + pa_close(fd); } return -1; } +static int proc_name_ours(pid_t pid, const char *procname) { +#ifdef __linux__ + char bn[PATH_MAX]; + FILE *f; + + pa_snprintf(bn, sizeof(bn), "/proc/%lu/stat", (unsigned long) pid); + + if (!(f = fopen(bn, "r"))) { + pa_log_info("Failed to open %s: %s", bn, pa_cstrerror(errno)); + return -1; + } else { + char *expected; + pa_bool_t good; + char stored[64]; + + if (!(fgets(stored, sizeof(stored), f))) { + pa_log_info("Failed to read from %s: %s", bn, feof(f) ? "EOF" : pa_cstrerror(errno)); + fclose(f); + return -1; + } + + fclose(f); + + expected = pa_sprintf_malloc("%lu (%s)", (unsigned long) pid, procname); + good = pa_startswith(stored, expected); + pa_xfree(expected); + +#if !defined(__OPTIMIZE__) + if (!good) { + /* libtool likes to rename our binary names ... */ + expected = pa_sprintf_malloc("%lu (lt-%s)", (unsigned long) pid, procname); + good = pa_startswith(stored, expected); + pa_xfree(expected); + } +#endif + + return !!good; + } +#endif + + return 1; +} + /* Create a new PID file for the current process. */ -int pa_pid_file_create(void) { +int pa_pid_file_create(const char *procname) { int fd = -1; int ret = -1; - char fn[PATH_MAX]; char t[20]; pid_t pid; size_t l; + char *fn; #ifdef OS_IS_WIN32 HANDLE process; #endif - pa_runtime_path("pid", fn, sizeof(fn)); + if (!(fn = pa_runtime_path("pid"))) + goto fail; if ((fd = open_pid_file(fn, O_CREAT|O_RDWR)) < 0) goto fail; if ((pid = read_pid(fn, fd)) == (pid_t) -1) - pa_log("corrupt PID file, overwriting."); + 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; + int ours = 1; + + if (procname) + if ((ours = proc_name_ours(pid, procname)) < 0) + goto fail; + + if (ours) { + pa_log("Daemon already running."); + ret = 1; + goto fail; + } } - pa_log("stale PID file, overwriting."); + 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)); + pa_log("Failed to truncate PID file '%s': %s", fn, pa_cstrerror(errno)); goto fail; } - - snprintf(t, sizeof(t), "%lu\n", (unsigned long) getpid()); + + 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."); + pa_log("Failed to write PID file."); goto fail; } ret = 0; - + fail: if (fd >= 0) { pa_lock_fd(fd, 0); - close(fd); + + if (pa_close(fd) < 0) { + pa_log("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + } } - + + pa_xfree(fn); + return ret; } /* Remove the PID file, if it is ours */ int pa_pid_file_remove(void) { int fd = -1; - char fn[PATH_MAX]; + char *fn; int ret = -1; pid_t pid; - pa_runtime_path("pid", fn, sizeof(fn)); + if (!(fn = pa_runtime_path("pid"))) + goto fail; if ((fd = open_pid_file(fn, O_RDWR)) < 0) { - pa_log_warn("WARNING: failed to open PID file '%s': %s", - fn, pa_cstrerror(errno)); + pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno)); goto fail; } @@ -207,37 +276,41 @@ int pa_pid_file_remove(void) { goto fail; if (pid != getpid()) { - pa_log("WARNING: PID file '%s' not mine!", fn); + pa_log("PID file '%s' not mine!", fn); goto fail; } if (ftruncate(fd, 0) < 0) { - pa_log_warn("WARNING: failed to truncate PID file '%s': %s", - fn, pa_cstrerror(errno)); + 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); + pa_close(fd); fd = -1; #endif if (unlink(fn) < 0) { - pa_log_warn("WARNING: failed to remove PID file '%s': %s", - fn, pa_cstrerror(errno)); + 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); - close(fd); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + } } + pa_xfree(fn); + return ret; } @@ -245,8 +318,8 @@ fail: * 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) { - return pa_pid_file_kill(0, pid); +int pa_pid_file_check_running(pid_t *pid, const char *procname) { + return pa_pid_file_kill(0, pid, procname); } #ifndef OS_IS_WIN32 @@ -254,39 +327,59 @@ int pa_pid_file_check_running(pid_t *pid) { /* 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) { +int pa_pid_file_kill(int sig, pid_t *pid, const char *procname) { int fd = -1; - char fn[PATH_MAX]; + char *fn; int ret = -1; pid_t _pid; +#ifdef __linux__ + char *e = NULL; +#endif if (!pid) pid = &_pid; - - pa_runtime_path("pid", fn, sizeof(fn)); - + + if (!(fn = pa_runtime_path("pid"))) + goto fail; + if ((fd = open_pid_file(fn, O_RDONLY)) < 0) goto fail; - + if ((*pid = read_pid(fn, fd)) == (pid_t) -1) goto fail; + if (procname) { + int ours; + + if ((ours = proc_name_ours(*pid, procname)) < 0) + goto fail; + + if (!ours) + goto fail; + } + ret = kill(*pid, sig); - + fail: - + if (fd >= 0) { pa_lock_fd(fd, 0); - close(fd); + pa_close(fd); } +#ifdef __linux__ + pa_xfree(e); +#endif + + pa_xfree(fn); + return ret; - + } #else /* OS_IS_WIN32 */ -int pa_pid_file_kill(int sig, pid_t *pid) { +int pa_pid_file_kill(int sig, pid_t *pid, const char *exe_name) { return -1; } diff --git a/src/pulsecore/pid.h b/src/pulsecore/pid.h index bd476b29..3c8a9de3 100644 --- a/src/pulsecore/pid.h +++ b/src/pulsecore/pid.h @@ -1,30 +1,30 @@ #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_create(const char *procname); int pa_pid_file_remove(void); -int pa_pid_file_check_running(pid_t *pid); -int pa_pid_file_kill(int sig, pid_t *pid); +int pa_pid_file_check_running(pid_t *pid, const char *procname); +int pa_pid_file_kill(int sig, pid_t *pid, const char *procname); #endif diff --git a/src/pulsecore/pipe.c b/src/pulsecore/pipe.c index 41ffb693..93d78a22 100644 --- a/src/pulsecore/pipe.c +++ b/src/pulsecore/pipe.c @@ -1,18 +1,18 @@ -/* $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 @@ -142,19 +142,19 @@ int pipe(int filedes[2]) { if ((addr.sin_port != peer.sin_port) || (addr.sin_addr.s_addr != peer.sin_addr.s_addr)) goto error; - close(listener); + pa_close(listener); return 0; error: - if (listener >= 0) - close(listener); - if (filedes[0] >= 0) - close(filedes[0]); - if (filedes[1] >= 0) - close(filedes[0]); - - return -1; + 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 index 21049e17..9a7e62c8 100644 --- a/src/pulsecore/pipe.h +++ b/src/pulsecore/pipe.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c index f459142a..8b3e79b9 100644 --- a/src/pulsecore/play-memblockq.c +++ b/src/pulsecore/play-memblockq.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2006-2008 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,103 +24,255 @@ #endif #include <stdlib.h> -#include <assert.h> #include <stdio.h> #include <string.h> #include <pulse/xmalloc.h> +#include <pulse/gccmacro.h> #include <pulsecore/sink-input.h> -#include <pulsecore/gccmacro.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/sample-util.h> #include "play-memblockq.h" -static void sink_input_kill(pa_sink_input *i) { - pa_memblockq *q; - assert(i); - assert(i->userdata); +typedef struct memblockq_stream { + pa_msgobject parent; + pa_core *core; + pa_sink_input *sink_input; + pa_memblockq *memblockq; +} memblockq_stream; - q = i->userdata; +enum { + MEMBLOCKQ_STREAM_MESSAGE_UNLINK, +}; - pa_sink_input_disconnect(i); - pa_sink_input_unref(i); +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_memblockq_free(q); + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + memblockq_stream_unref(u); } -static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) { - pa_memblockq *q; - assert(i); - assert(chunk); - assert(i->userdata); +static void memblockq_stream_free(pa_object *o) { + memblockq_stream *u = MEMBLOCKQ_STREAM(o); + pa_assert(u); - q = i->userdata; + if (u->memblockq) + pa_memblockq_free(u->memblockq); - return pa_memblockq_peek(q, chunk); + pa_xfree(u); } -static void si_kill(PA_GCC_UNUSED pa_mainloop_api *m, void *i) { - sink_input_kill(i); +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_drop(pa_sink_input *i, const pa_memchunk*chunk, size_t length) { - pa_memblockq *q; - - assert(i); - assert(length > 0); - assert( i->userdata); - - q = i->userdata; +static void sink_input_kill_cb(pa_sink_input *i) { + memblockq_stream *u; - pa_memblockq_drop(q, chunk, length); + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); - if (pa_memblockq_get_length(q) <= 0) - pa_mainloop_api_once(i->sink->core->mainloop, si_kill, i); + memblockq_stream_unlink(u); } -int pa_play_memblockq( +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); +} + +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return -1; + + if (pa_memblockq_peek(u->memblockq, chunk) < 0) { + + if (pa_sink_input_safe_to_remove(i)) { + + pa_memblockq_free(u->memblockq); + u->memblockq = NULL; + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMBLOCKQ_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL); + } + + return -1; + } + + chunk->length = PA_MIN(chunk->length, nbytes); + pa_memblockq_drop(u->memblockq, chunk->length); + + return 0; +} + +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + pa_assert(nbytes > 0); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return; + + pa_memblockq_rewind(u->memblockq, nbytes); +} + +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return; + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); +} + +pa_sink_input* pa_memblockq_sink_input_new( pa_sink *sink, - const char *name, const pa_sample_spec *ss, const pa_channel_map *map, pa_memblockq *q, - pa_cvolume *volume) { - - pa_sink_input *si; + pa_cvolume *volume, + pa_proplist *p) { + + memblockq_stream *u = NULL; pa_sink_input_new_data data; - assert(sink); - assert(ss); - assert(q); + pa_assert(sink); + pa_assert(ss); - if (pa_memblockq_get_length(q) <= 0) { - pa_memblockq_free(q); - return 0; - } + /* We allow creating this stream with no q set, so that it can be + * filled in later */ - if (volume && pa_cvolume_is_muted(volume)) { - pa_memblockq_free(q); - return 0; - } + u = pa_msgobject_new(memblockq_stream); + u->parent.parent.free = memblockq_stream_free; + u->parent.process_msg = memblockq_stream_process_msg; + u->core = sink->core; + u->sink_input = NULL; + u->memblockq = NULL; pa_sink_input_new_data_init(&data); data.sink = sink; - data.name = name; data.driver = __FILE__; - pa_sink_input_new_data_set_channel_map(&data, map); 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 (!(si = pa_sink_input_new(sink->core, &data, 0))) + pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p); + + u->sink_input = pa_sink_input_new(sink->core, &data, 0); + pa_sink_input_new_data_done(&data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->userdata = u; + + if (q) + pa_memblockq_sink_input_set_queue(u->sink_input, q); + + /* The reference to u is dangling here, because we want + * to keep this stream around until it is fully played. */ + + /* This sink input is not "put" yet, i.e. pa_sink_input_put() has + * not been called! */ + + return pa_sink_input_ref(u->sink_input); + +fail: + if (u) + memblockq_stream_unref(u); + + return NULL; +} + +int pa_play_memblockq( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume, + pa_proplist *p, + uint32_t *sink_input_index) { + + pa_sink_input *i; + + pa_assert(sink); + pa_assert(ss); + pa_assert(q); + + if (!(i = pa_memblockq_sink_input_new(sink, ss, map, q, volume, p))) return -1; - si->peek = sink_input_peek; - si->drop = sink_input_drop; - si->kill = sink_input_kill; - - si->userdata = q; + pa_sink_input_put(i); + + if (sink_input_index) + *sink_input_index = i->index; + + pa_sink_input_unref(i); - pa_sink_notify(si->sink); - return 0; } + +void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if ((u->memblockq = q)) { + pa_memblockq_set_prebuf(q, 0); + pa_memblockq_set_silence(q, NULL); + pa_memblockq_willneed(q); + } +} diff --git a/src/pulsecore/play-memblockq.h b/src/pulsecore/play-memblockq.h index 9b96efe3..1a42867b 100644 --- a/src/pulsecore/play-memblockq.h +++ b/src/pulsecore/play-memblockq.h @@ -1,21 +1,21 @@ #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 @@ -25,12 +25,23 @@ #include <pulsecore/sink.h> #include <pulsecore/memblockq.h> +pa_sink_input* pa_memblockq_sink_input_new( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume, + pa_proplist *p); + +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); + pa_cvolume *cvolume, + pa_proplist *p, + uint32_t *sink_input_index); #endif diff --git a/src/pulsecore/play-memchunk.c b/src/pulsecore/play-memchunk.c index b711c98c..0dd48251 100644 --- a/src/pulsecore/play-memchunk.c +++ b/src/pulsecore/play-memchunk.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,103 +24,41 @@ #endif #include <stdlib.h> -#include <assert.h> #include <stdio.h> #include <string.h> #include <pulse/xmalloc.h> +#include <pulse/gccmacro.h> #include <pulsecore/sink-input.h> -#include <pulsecore/gccmacro.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/play-memblockq.h> #include "play-memchunk.h" -static void sink_input_kill(pa_sink_input *i) { - pa_memchunk *c; - assert(i && i->userdata); - c = i->userdata; - - pa_sink_input_disconnect(i); - pa_sink_input_unref(i); - - pa_memblock_unref(c->memblock); - pa_xfree(c); -} - -static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) { - pa_memchunk *c; - assert(i && chunk && i->userdata); - c = i->userdata; - - if (c->length <= 0) - return -1; - - assert(c->memblock); - *chunk = *c; - pa_memblock_ref(c->memblock); - - return 0; -} - -static void si_kill(PA_GCC_UNUSED pa_mainloop_api *m, void *i) { - sink_input_kill(i); -} - -static void sink_input_drop(pa_sink_input *i, const pa_memchunk*chunk, size_t length) { - pa_memchunk *c; - assert(i && length && i->userdata); - c = i->userdata; - - assert(!memcmp(chunk, c, sizeof(chunk))); - assert(length <= c->length); - - c->length -= length; - c->index += length; - - if (c->length <= 0) - pa_mainloop_api_once(i->sink->core->mainloop, si_kill, i); -} - 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) { - - pa_sink_input *si; - pa_memchunk *nchunk; - pa_sink_input_new_data data; + pa_cvolume *volume, + pa_proplist *p, + uint32_t *sink_input_index) { - assert(sink); - assert(ss); - assert(chunk); + pa_memblockq *q; + int r; - if (volume && pa_cvolume_is_muted(volume)) - return 0; + pa_assert(sink); + pa_assert(ss); + pa_assert(chunk); - 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 (!(si = pa_sink_input_new(sink->core, &data, 0))) - return -1; + q = pa_memblockq_new(0, chunk->length, 0, pa_frame_size(ss), 1, 1, 0, NULL); + pa_assert_se(pa_memblockq_push(q, chunk) >= 0); - si->peek = sink_input_peek; - si->drop = sink_input_drop; - si->kill = sink_input_kill; - - si->userdata = nchunk = pa_xnew(pa_memchunk, 1); - *nchunk = *chunk; - - pa_memblock_ref(chunk->memblock); + if ((r = pa_play_memblockq(sink, ss, map, q, volume, p, sink_input_index)) < 0) { + pa_memblockq_free(q); + return r; + } - pa_sink_notify(si->sink); - return 0; } diff --git a/src/pulsecore/play-memchunk.h b/src/pulsecore/play-memchunk.h index 3d5b8cc6..c312ae82 100644 --- a/src/pulsecore/play-memchunk.h +++ b/src/pulsecore/play-memchunk.h @@ -1,21 +1,21 @@ #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 @@ -27,10 +27,11 @@ 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); + pa_cvolume *cvolume, + pa_proplist *p, + uint32_t *sink_input_index); #endif diff --git a/src/pulsecore/poll.c b/src/pulsecore/poll.c index 289e0cdf..88ac21e4 100644 --- a/src/pulsecore/poll.c +++ b/src/pulsecore/poll.c @@ -1,31 +1,30 @@ -/* $Id$ */ - -/*** - Copyright (C) 1994, 1996, 1997 Free Software Foundation, Inc. - Copyright (C) 2005, Cendio AB. - This file is part of PulseAudio. - Based on work for the GNU C Library. -***/ /*** 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. @@ -44,7 +43,7 @@ #include "winsock.h" -#ifndef HAVE_SYS_POLL_H +#ifndef HAVE_POLL_H #include <pulsecore/core-util.h> diff --git a/src/pulsecore/poll.h b/src/pulsecore/poll.h index c3d486e1..86c37a08 100644 --- a/src/pulsecore/poll.h +++ b/src/pulsecore/poll.h @@ -1,32 +1,29 @@ -/* $Id$ */ - -/*** - Compatibility definitions for System V `poll' interface. - Copyright (C) 1994,96,97,98,99,2000,2001,2004 Free Software Foundation, Inc. - Copyright (C) 2005, Cendio AB. - This file is part of PulseAudio. - Based on work for the GNU C Library. -***/ - /*** 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. */ diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c new file mode 100644 index 00000000..6005775e --- /dev/null +++ b/src/pulsecore/proplist-util.c @@ -0,0 +1,118 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to 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 <locale.h> + +#include <pulse/proplist.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-util.h> + +#include "proplist-util.h" + +void pa_init_proplist(pa_proplist *p) { + int a, b; +#ifndef HAVE_DECL_ENVIRON + extern char **environ; +#endif + char **e; + + pa_assert(p); + + for (e = environ; *e; e++) { + + if (pa_startswith(*e, "PULSE_PROP_")) { + size_t kl = strcspn(*e+11, "="); + char *k; + + if ((*e)[11+kl] != '=') + continue; + + if (!pa_utf8_valid(*e+11+kl+1)) + continue; + + k = pa_xstrndup(*e+11, kl); + + if (pa_proplist_contains(p, k)) { + pa_xfree(k); + continue; + } + + pa_proplist_sets(p, k, *e+11+kl+1); + pa_xfree(k); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_ID)) { + char t[32]; + pa_snprintf(t, sizeof(t), "%lu", (unsigned long) getpid()); + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_ID, t); + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_USER)) { + char t[64]; + if (pa_get_user_name(t, sizeof(t))) { + char *c = pa_utf8_filter(t); + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_USER, c); + pa_xfree(c); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_HOST)) { + char t[64]; + if (pa_get_host_name(t, sizeof(t))) { + char *c = pa_utf8_filter(t); + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, c); + pa_xfree(c); + } + } + + a = pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_BINARY); + b = pa_proplist_contains(p, PA_PROP_APPLICATION_NAME); + + if (!a || !b) { + char t[PATH_MAX]; + if (pa_get_binary_name(t, sizeof(t))) { + char *c = pa_utf8_filter(t); + + if (!a) + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_BINARY, c); + if (!b) + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, c); + + pa_xfree(c); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_LANGUAGE)) { + const char *l; + + if ((l = setlocale(LC_MESSAGES, NULL))) + pa_proplist_sets(p, PA_PROP_APPLICATION_LANGUAGE, l); + } +} diff --git a/src/pulsecore/proplist-util.h b/src/pulsecore/proplist-util.h new file mode 100644 index 00000000..c6bdc103 --- /dev/null +++ b/src/pulsecore/proplist-util.h @@ -0,0 +1,29 @@ +#ifndef fooproplistutilutilhfoo +#define fooproplistutilutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulse/proplist.h> + +void pa_init_proplist(pa_proplist *p); + +#endif diff --git a/src/pulsecore/props.c b/src/pulsecore/props.c index 8879b7aa..23d432ec 100644 --- a/src/pulsecore/props.c +++ b/src/pulsecore/props.c @@ -1,29 +1,31 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General 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 <assert.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <pulse/xmalloc.h> - #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "props.h" @@ -35,9 +37,11 @@ typedef struct pa_property { /* Allocate a new property object */ static pa_property* property_new(const char *name, void *data) { pa_property* p; - assert(name && data); - - p = pa_xmalloc(sizeof(pa_property)); + + pa_assert(name); + pa_assert(data); + + p = pa_xnew(pa_property, 1); p->name = pa_xstrdup(name); p->data = data; @@ -46,7 +50,7 @@ static pa_property* property_new(const char *name, void *data) { /* Free a property object */ static void property_free(pa_property *p) { - assert(p); + pa_assert(p); pa_xfree(p->name); pa_xfree(p); @@ -54,7 +58,10 @@ static void property_free(pa_property *p) { void* pa_property_get(pa_core *c, const char *name) { pa_property *p; - assert(c && name && c->properties); + + pa_assert(c); + pa_assert(name); + pa_assert(c->properties); if (!(p = pa_hashmap_get(c->properties, name))) return NULL; @@ -64,7 +71,11 @@ void* pa_property_get(pa_core *c, const char *name) { int pa_property_set(pa_core *c, const char *name, void *data) { pa_property *p; - assert(c && name && data && c->properties); + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(c->properties); if (pa_hashmap_get(c->properties, name)) return -1; @@ -76,45 +87,51 @@ int pa_property_set(pa_core *c, const char *name, void *data) { int pa_property_remove(pa_core *c, const char *name) { pa_property *p; - assert(c && name && c->properties); + + 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) { - assert(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) { - assert(c); + pa_assert(c); if (!c->properties) return; - assert(!pa_hashmap_size(c->properties)); + 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; - assert(c && s); + + 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) { - assert(c && name); + 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 index 39b7ca68..95db229b 100644 --- a/src/pulsecore/props.h +++ b/src/pulsecore/props.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/protocol-cli.c b/src/pulsecore/protocol-cli.c index 81ce5e8f..30cb475d 100644 --- a/src/pulsecore/protocol-cli.c +++ b/src/pulsecore/protocol-cli.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,13 +23,13 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <pulse/xmalloc.h> #include <pulsecore/cli.h> #include <pulsecore/log.h> +#include <pulsecore/macro.h> #include "protocol-cli.h" @@ -45,7 +45,8 @@ struct pa_protocol_cli { static void cli_eof_cb(pa_cli*c, void*userdata) { pa_protocol_cli *p = userdata; - assert(p); + pa_assert(p); + pa_idxset_remove_by_data(p->connections, c, NULL); pa_cli_free(c); } @@ -53,16 +54,18 @@ static void cli_eof_cb(pa_cli*c, void*userdata) { static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { pa_protocol_cli *p = userdata; pa_cli *c; - assert(s && io && p); + + 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); - assert(c); pa_cli_set_eof_callback(c, cli_eof_cb, p); pa_idxset_put(p->connections, c, NULL); @@ -70,26 +73,29 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) 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; - assert(core && server); - p = pa_xmalloc(sizeof(pa_protocol_cli)); + 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->server = pa_socket_server_ref(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) { - assert(p); + pa_assert(p); + pa_cli_free(p); } void pa_protocol_cli_free(pa_protocol_cli *p) { - assert(p); + pa_assert(p); pa_idxset_free(p->connections, free_connection, NULL); pa_socket_server_unref(p->server); diff --git a/src/pulsecore/protocol-cli.h b/src/pulsecore/protocol-cli.h index 84101e14..8922ac62 100644 --- a/src/pulsecore/protocol-cli.h +++ b/src/pulsecore/protocol-cli.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index 65b93eb4..db1b4305 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -1,18 +1,19 @@ -/* $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 @@ -26,7 +27,6 @@ #include <errno.h> #include <string.h> #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <limits.h> @@ -50,6 +50,8 @@ #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" @@ -66,21 +68,25 @@ #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 MAX_CACHE_SAMPLE_SIZE (2048000) + +#define DEFAULT_SINK_LATENCY (150*PA_USEC_PER_MSEC) +#define DEFAULT_SOURCE_LATENCY (150*PA_USEC_PER_MSEC) #define SCACHE_PREFIX "esound." /* This is heavily based on esound's code */ -struct connection { +typedef struct connection { + pa_msgobject parent; + uint32_t index; - int dead; + pa_bool_t dead; pa_protocol_esound *protocol; pa_iochannel *io; pa_client *client; - int authorized, swap_byte_order; + pa_bool_t authorized, swap_byte_order; void *write_data; size_t write_data_alloc, write_data_index, write_data_length; void *read_data; @@ -93,10 +99,12 @@ struct connection { pa_defer_event *defer_event; char *original_name; - + struct { pa_memblock *current_memblock; - size_t memblock_index, fragment_size; + size_t memblock_index; + pa_atomic_t missing; + pa_bool_t underrun; } playback; struct { @@ -106,46 +114,63 @@ struct connection { } 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 { - int public; pa_module *module; pa_core *core; + pa_bool_t 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)(struct connection *c, esd_proto_t request, const void *data, size_t 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, const pa_memchunk *chunk, size_t length); -static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk); +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes); static void sink_input_kill_cb(pa_sink_input *i); -static pa_usec_t sink_input_get_latency_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(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_stream_play(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_stream_record(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_get_latency(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_server_info(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_all_info(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_stream_pan(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_sample_cache(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_sample_free_or_play(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_sample_get_id(struct connection *c, esd_proto_t request, const void *data, size_t length); -static int esd_proto_standby_or_resume(struct connection *c, esd_proto_t request, const void *data, size_t length); +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] = { @@ -177,30 +202,61 @@ static struct proto_handler proto_map[ESD_PROTO_MAX] = { { 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_free(struct connection *c) { - assert(c); - pa_idxset_remove_by_data(c->protocol->connections, c, NULL); +static void connection_unlink(connection *c) { + pa_assert(c); - if (c->state == ESD_STREAMING_DATA) - c->protocol->n_player--; - - pa_client_free(c->client); + if (!c->protocol) + return; if (c->sink_input) { - pa_sink_input_disconnect(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_disconnect(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) @@ -208,58 +264,48 @@ static void connection_free(struct connection *c) { if (c->playback.current_memblock) pa_memblock_unref(c->playback.current_memblock); - + pa_xfree(c->read_data); pa_xfree(c->write_data); - if (c->io) - pa_iochannel_free(c->io); - - if (c->defer_event) - c->protocol->core->mainloop->defer_free(c->defer_event); - if (c->scache.memchunk.memblock) pa_memblock_unref(c->scache.memchunk.memblock); pa_xfree(c->scache.name); - if (c->auth_timeout_event) - c->protocol->core->mainloop->time_free(c->auth_timeout_event); - pa_xfree(c->original_name); pa_xfree(c); } -static void connection_write_prepare(struct connection *c, size_t length) { +static void connection_write_prepare(connection *c, size_t length) { size_t t; - assert(c); + 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); - assert(c->write_data); + pa_assert(c->write_data); } -static void connection_write(struct connection *c, const void *data, size_t length) { +static void connection_write(connection *c, const void *data, size_t length) { size_t i; - assert(c); + pa_assert(c); - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable); c->protocol->core->mainloop->defer_enable(c->defer_event, 1); connection_write_prepare(c, length); - assert(c->write_data); + pa_assert(c->write_data); i = c->write_data_length; c->write_data_length += length; - - memcpy((char*)c->write_data + i, data, length); + + memcpy((uint8_t*) c->write_data + i, data, length); } -static void format_esd2native(int format, int swap_bytes, pa_sample_spec *ss) { - assert(ss); +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) @@ -270,27 +316,29 @@ static void format_esd2native(int format, int swap_bytes, pa_sample_spec *ss) { 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, string) do { \ +#define CHECK_VALIDITY(expression, ...) do { \ if (!(expression)) { \ - pa_log_warn(__FILE__ ": " string); \ + pa_log_warn(__FILE__ ": " __VA_ARGS__); \ return -1; \ } \ } while(0); /*** esound commands ***/ -static int esd_proto_connect(struct connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { +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; - assert(length == (ESD_KEY_LEN + sizeof(uint32_t))); + 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) { @@ -298,7 +346,7 @@ static int esd_proto_connect(struct connection *c, PA_GCC_UNUSED esd_proto_t req return -1; } - c->authorized = 1; + c->authorized = TRUE; if (c->auth_timeout_event) { c->protocol->core->mainloop->time_free(c->auth_timeout_event); c->auth_timeout_event = NULL; @@ -309,11 +357,11 @@ static int esd_proto_connect(struct connection *c, PA_GCC_UNUSED esd_proto_t req memcpy(&ekey, data, sizeof(uint32_t)); if (ekey == ESD_ENDIAN_KEY) - c->swap_byte_order = 0; + c->swap_byte_order = FALSE; else if (ekey == ESD_SWAP_ENDIAN_KEY) - c->swap_byte_order = 1; + c->swap_byte_order = TRUE; else { - pa_log("client sent invalid endian key"); + pa_log_warn("Client sent invalid endian key"); return -1; } @@ -322,7 +370,7 @@ static int esd_proto_connect(struct connection *c, PA_GCC_UNUSED esd_proto_t req return 0; } -static int esd_proto_stream_play(struct connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { +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; @@ -330,15 +378,17 @@ static int esd_proto_stream_play(struct connection *c, PA_GCC_UNUSED esd_proto_t pa_sink *sink = NULL; pa_sink_input_new_data sdata; - assert(c && length == (sizeof(int32_t)*2+ESD_NAME_MAX)); - + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX)); + memcpy(&format, data, sizeof(int32_t)); - format = MAYBE_INT32_SWAP(c->swap_byte_order, format); - data = (const char*)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 = MAYBE_INT32_SWAP(c->swap_byte_order, rate); - data = (const char*)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); @@ -347,57 +397,65 @@ static int esd_proto_stream_play(struct connection *c, PA_GCC_UNUSED esd_proto_t 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"); + CHECK_VALIDITY(sink, "No such sink: %s", c->protocol->sink_name); } - strncpy(name, data, sizeof(name)); - name[sizeof(name)-1] = 0; + pa_strlcpy(name, data, sizeof(name)); utf8_name = pa_utf8_filter(name); pa_client_set_name(c->client, utf8_name); pa_xfree(utf8_name); - + c->original_name = pa_xstrdup(name); - assert(!c->sink_input && !c->input_memblockq); + 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; - + sdata.sink = sink; + pa_proplist_update(sdata.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_sink_input_new_data_set_sample_spec(&sdata, &ss); + c->sink_input = pa_sink_input_new(c->protocol->core, &sdata, 0); + pa_sink_input_new_data_done(&sdata); + CHECK_VALIDITY(c->sink_input, "Failed to create sink input."); - l = (size_t) (pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS); + l = (size_t) (pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS); c->input_memblockq = pa_memblockq_new( 0, l, - 0, + l, pa_frame_size(&ss), (size_t) -1, l/PLAYBACK_BUFFER_FRAGMENTS, + 0, NULL); - pa_iochannel_socket_set_rcvbuf(c->io, l/PLAYBACK_BUFFER_FRAGMENTS*2); - c->playback.fragment_size = l/10; + pa_iochannel_socket_set_rcvbuf(c->io, l); - c->sink_input->peek = sink_input_peek_cb; - c->sink_input->drop = sink_input_drop_cb; + c->sink_input->parent.process_msg = sink_input_process_msg; + c->sink_input->pop = sink_input_pop_cb; + c->sink_input->process_rewind = sink_input_process_rewind_cb; + c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; c->sink_input->kill = sink_input_kill_cb; - c->sink_input->get_latency = sink_input_get_latency_cb; c->sink_input->userdata = c; + pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY); + c->state = ESD_STREAMING_DATA; c->protocol->n_player++; - + + pa_atomic_store(&c->playback.missing, pa_memblockq_missing(c->input_memblockq)); + + pa_sink_input_put(c->sink_input); + return 0; } -static int esd_proto_stream_record(struct 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) { char name[ESD_NAME_MAX], *utf8_name; int32_t format, rate; pa_source *source = NULL; @@ -405,15 +463,17 @@ static int esd_proto_stream_record(struct connection *c, esd_proto_t request, co size_t l; pa_source_output_new_data sdata; - assert(c && length == (sizeof(int32_t)*2+ESD_NAME_MAX)); - + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX)); + memcpy(&format, data, sizeof(int32_t)); - format = MAYBE_INT32_SWAP(c->swap_byte_order, format); - data = (const char*)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 = MAYBE_INT32_SWAP(c->swap_byte_order, rate); - data = (const char*)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); @@ -433,7 +493,7 @@ static int esd_proto_stream_record(struct connection *c, esd_proto_t request, co return -1; } } else { - assert(request == ESD_PROTO_STREAM_REC); + 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))) { @@ -442,57 +502,65 @@ static int esd_proto_stream_record(struct connection *c, esd_proto_t request, co } } } - - strncpy(name, data, sizeof(name)); - name[sizeof(name)-1] = 0; + + pa_strlcpy(name, data, sizeof(name)); utf8_name = pa_utf8_filter(name); pa_client_set_name(c->client, utf8_name); pa_xfree(utf8_name); - + c->original_name = pa_xstrdup(name); - assert(!c->output_memblockq && !c->source_output); + 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->sink_input, "Failed to create source_output."); + sdata.source = source; + pa_proplist_update(sdata.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_source_output_new_data_set_sample_spec(&sdata, &ss); + + c->source_output = pa_source_output_new(c->protocol->core, &sdata, 0); + pa_source_output_new_data_done(&sdata); + + CHECK_VALIDITY(c->source_output, "Failed to create source output."); - l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); + l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); c->output_memblockq = pa_memblockq_new( 0, l, - 0, + l, pa_frame_size(&ss), 1, 0, + 0, NULL); - pa_iochannel_socket_set_sndbuf(c->io, l/RECORD_BUFFER_FRAGMENTS*2); - + pa_iochannel_socket_set_sndbuf(c->io, l); + c->source_output->push = source_output_push_cb; c->source_output->kill = source_output_kill_cb; c->source_output->get_latency = source_output_get_latency_cb; c->source_output->userdata = c; + pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + c->state = ESD_STREAMING_DATA; c->protocol->n_player++; - + + pa_source_output_put(c->source_output); + return 0; } -static int esd_proto_get_latency(struct connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { +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; - assert(c && !data && length == 0); + 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; @@ -500,18 +568,20 @@ static int esd_proto_get_latency(struct connection *c, PA_GCC_UNUSED esd_proto_t double usec = pa_sink_get_latency(sink); latency = (int) ((usec*44100)/1000000); } - - latency = MAYBE_INT32_SWAP(c->swap_byte_order, latency); + + 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(struct connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { +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; - assert(c && data && length == sizeof(int32_t)); + 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; @@ -522,23 +592,25 @@ static int esd_proto_server_info(struct connection *c, PA_GCC_UNUSED esd_proto_t response = 0; connection_write(c, &response, sizeof(int32_t)); - rate = MAYBE_INT32_SWAP(c->swap_byte_order, rate); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); connection_write(c, &rate, sizeof(int32_t)); - format = MAYBE_INT32_SWAP(c->swap_byte_order, format); + 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(struct 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) { size_t t, k, s; - struct connection *conn; + connection *conn; uint32_t idx = PA_IDXSET_INVALID; unsigned nsamples; char terminator[sizeof(int32_t)*6+ESD_NAME_MAX]; - assert(c && data && length == sizeof(int32_t)); - + connection_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); + if (esd_proto_server_info(c, request, data, length) < 0) return -1; @@ -558,8 +630,8 @@ static int esd_proto_all_info(struct connection *c, esd_proto_t request, const v if (conn->state != ESD_STREAMING_DATA) continue; - assert(t >= k*2+s); - + 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; @@ -567,113 +639,115 @@ static int esd_proto_all_info(struct connection *c, esd_proto_t request, const v rvolume = (volume.values[1]*ESD_VOLUME_BASE)/PA_VOLUME_NORM; format = format_native2esd(&conn->sink_input->sample_spec); } - + /* id */ - id = MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) (conn->index+1)); + 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); + else if (conn->client && pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME)) + strncpy(name, pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME), ESD_NAME_MAX); connection_write(c, name, ESD_NAME_MAX); /* rate */ - rate = MAYBE_INT32_SWAP(c->swap_byte_order, rate); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); connection_write(c, &rate, sizeof(int32_t)); /* left */ - lvolume = MAYBE_INT32_SWAP(c->swap_byte_order, lvolume); + lvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, lvolume); connection_write(c, &lvolume, sizeof(int32_t)); /*right*/ - rvolume = MAYBE_INT32_SWAP(c->swap_byte_order, rvolume); + rvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rvolume); connection_write(c, &rvolume, sizeof(int32_t)); /*format*/ - format = MAYBE_INT32_SWAP(c->swap_byte_order, format); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); connection_write(c, &format, sizeof(int32_t)); t -= k; } - assert(t == s*(nsamples+1)+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]; - assert(t >= s*2); + pa_assert(t >= s*2); /* id */ - id = MAYBE_INT32_SWAP(c->swap_byte_order, (int) (ce->index+1)); + 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 - snprintf(name, ESD_NAME_MAX, "native.%s", ce->name); + pa_snprintf(name, ESD_NAME_MAX, "native.%s", ce->name); connection_write(c, name, ESD_NAME_MAX); - + /* rate */ - rate = MAYBE_UINT32_SWAP(c->swap_byte_order, ce->sample_spec.rate); + rate = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, ce->sample_spec.rate); connection_write(c, &rate, sizeof(int32_t)); - + /* left */ - lvolume = MAYBE_UINT32_SWAP(c->swap_byte_order, (ce->volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); + 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 = MAYBE_UINT32_SWAP(c->swap_byte_order, (ce->volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); + 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 = MAYBE_INT32_SWAP(c->swap_byte_order, format_native2esd(&ce->sample_spec)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format_native2esd(&ce->sample_spec)); connection_write(c, &format, sizeof(int32_t)); /*length*/ - len = MAYBE_INT32_SWAP(c->swap_byte_order, (int) ce->memchunk.length); + len = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int) ce->memchunk.length); connection_write(c, &len, sizeof(int32_t)); t -= s; } } - assert(t == s); + pa_assert(t == s); connection_write(c, terminator, s); return 0; } -static int esd_proto_stream_pan(struct connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { +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; - struct connection *conn; + connection *conn; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)*3); - assert(c && data && length == sizeof(int32_t)*3); - memcpy(&idx, data, sizeof(uint32_t)); - idx = MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1; + 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 = MAYBE_UINT32_SWAP(c->swap_byte_order, lvolume); + lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, lvolume); data = (const char*)data + sizeof(uint32_t); memcpy(&rvolume, data, sizeof(uint32_t)); - rvolume = MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume); + 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) { @@ -687,71 +761,73 @@ static int esd_proto_stream_pan(struct connection *c, PA_GCC_UNUSED esd_proto_t ok = 0; connection_write(c, &ok, sizeof(int32_t)); - + return 0; } -static int esd_proto_sample_cache(struct connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { +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]; - assert(c && data && length == (ESD_NAME_MAX+3*sizeof(int32_t))); + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (ESD_NAME_MAX+3*sizeof(int32_t))); memcpy(&format, data, sizeof(int32_t)); - format = MAYBE_INT32_SWAP(c->swap_byte_order, format); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); data = (const char*)data + sizeof(int32_t); memcpy(&rate, data, sizeof(int32_t)); - rate = MAYBE_INT32_SWAP(c->swap_byte_order, rate); + 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 = MAYBE_INT32_SWAP(c->swap_byte_order, sc_length); + 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."); + 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; + pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); - - assert(!c->scache.memchunk.memblock); + + 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; - assert(!c->scache.name); + 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); + pa_scache_add_item(c->protocol->core, c->scache.name, NULL, NULL, NULL, c->client->proplist, &idx); idx += 1; connection_write(c, &idx, sizeof(uint32_t)); - + return 0; } -static int esd_proto_sample_get_id(struct connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { +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]; - assert(c && data && length == ESD_NAME_MAX); + 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; + pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); @@ -764,41 +840,45 @@ static int esd_proto_sample_get_id(struct connection *c, PA_GCC_UNUSED esd_proto return 0; } -static int esd_proto_sample_free_or_play(struct 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) { int32_t ok; const char *name; uint32_t idx; - assert(c && data && length == sizeof(int32_t)); + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); memcpy(&idx, data, sizeof(uint32_t)); - idx = MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1; + 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) + if (pa_scache_play_item(c->protocol->core, name, sink, PA_VOLUME_NORM, c->client->proplist, NULL) >= 0) ok = idx + 1; } else { - assert(request == ESD_PROTO_SAMPLE_FREE); + 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(struct connection *c, PA_GCC_UNUSED esd_proto_t request, PA_GCC_UNUSED const void *data, PA_GCC_UNUSED size_t length) { +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; @@ -811,20 +891,21 @@ static int esd_proto_standby_or_resume(struct connection *c, PA_GCC_UNUSED esd_p /*** client callbacks ***/ static void client_kill_cb(pa_client *c) { - assert(c && c->userdata); - connection_free(c->userdata); + pa_assert(c); + + connection_unlink(CONNECTION(c->userdata)); } /*** pa_iochannel callbacks ***/ -static int do_read(struct connection *c) { - assert(c && c->io); +static int do_read(connection *c) { + connection_assert_ref(c); + +/* pa_log("READ"); */ -/* pa_log("READ"); */ - if (c->state == ESD_NEXT_REQUEST) { ssize_t r; - assert(c->read_data_length < sizeof(c->request)); + 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"); @@ -833,8 +914,8 @@ static int do_read(struct connection *c) { if ((c->read_data_length+= r) >= sizeof(c->request)) { struct proto_handler *handler; - - c->request = MAYBE_INT32_SWAP(c->swap_byte_order, c->request); + + 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."); @@ -849,18 +930,18 @@ static int do_read(struct connection *c) { 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); - assert(c->read_data); - + pa_assert(c->read_data); + c->state = ESD_NEEDS_REQDATA; c->read_data_length = 0; } @@ -870,22 +951,25 @@ static int do_read(struct connection *c) { ssize_t r; struct proto_handler *handler = proto_map+c->request; - assert(handler->proc); - - assert(c->read_data && c->read_data_length < handler->data_length); + 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; - assert(handler->proc); + 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; } @@ -893,28 +977,30 @@ static int do_read(struct connection *c) { ssize_t r; void *p; - assert(c->scache.memchunk.memblock); - assert(c->scache.name); - assert(c->scache.memchunk.index < c->scache.memchunk.length); + 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); - - if ((r = pa_iochannel_read(c->io, (uint8_t*) p+c->scache.memchunk.index, c->scache.memchunk.length-c->scache.memchunk.index)) <= 0) { - pa_memblock_release(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; } - pa_memblock_release(c->scache.memchunk.memblock); - c->scache.memchunk.index += r; - assert(c->scache.memchunk.index <= c->scache.memchunk.length); - + 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_scache_add_item(c->protocol->core, c->scache.name, &c->scache.sample_spec, NULL, &c->scache.memchunk, c->client->proplist, &idx); pa_memblock_unref(c->scache.memchunk.memblock); c->scache.memchunk.memblock = NULL; @@ -928,127 +1014,133 @@ static int do_read(struct connection *c) { idx += 1; connection_write(c, &idx, sizeof(uint32_t)); } - + } else if (c->state == ESD_STREAMING_DATA && c->sink_input) { pa_memchunk chunk; ssize_t r; size_t l; void *p; + size_t space; - assert(c->input_memblockq); + pa_assert(c->input_memblockq); /* pa_log("STREAMING_DATA"); */ - if (!(l = pa_memblockq_missing(c->input_memblockq))) + 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) { + + space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index; - if (c->playback.current_memblock) - if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) { + if (space <= 0) { pa_memblock_unref(c->playback.current_memblock); c->playback.current_memblock = NULL; - c->playback.memblock_index = 0; } - + } + if (!c->playback.current_memblock) { - c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, c->playback.fragment_size*2); - assert(c->playback.current_memblock); - assert(pa_memblock_get_length(c->playback.current_memblock) >= l); + pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) -1)); c->playback.memblock_index = 0; + + space = pa_memblock_get_length(c->playback.current_memblock); } + if (l > space) + l = space; + p = pa_memblock_acquire(c->playback.current_memblock); - - if ((r = pa_iochannel_read(c->io, (uint8_t*) p+c->playback.memblock_index, l)) <= 0) { - pa_memblock_release(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; } - pa_memblock_release(c->playback.current_memblock); - chunk.memblock = c->playback.current_memblock; chunk.index = c->playback.memblock_index; chunk.length = r; - assert(chunk.memblock); c->playback.memblock_index += r; - - assert(c->input_memblockq); - pa_memblockq_push_align(c->input_memblockq, &chunk); - assert(c->sink_input); - pa_sink_notify(c->sink_input->sink); + + 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(struct connection *c) { - assert(c && c->io); +static int do_write(connection *c) { + connection_assert_ref(c); /* pa_log("WRITE"); */ - + if (c->write_data_length) { ssize_t r; - - assert(c->write_data_index < c->write_data_length); + + 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; - assert(c->output_memblockq); if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) return 0; - - assert(chunk.memblock); - assert(chunk.length); - p = pa_memblock_acquire(chunk.memblock); - - if ((r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length)) < 0) { - pa_memblock_release(chunk.memblock); - pa_memblock_unref(chunk.memblock); - pa_log("write(): %s", pa_cstrerror(errno)); - return -1; - } + 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_memblockq_drop(c->output_memblockq, &chunk, r); pa_memblock_unref(chunk.memblock); - pa_source_notify(c->source_output->source); + 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(struct connection *c) { - assert(c); +static void do_work(connection *c) { + connection_assert_ref(c); - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable); c->protocol->core->mainloop->defer_enable(c->defer_event, 0); if (c->dead) return; - if (pa_iochannel_is_readable(c->io)) { + 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)) + if (c->state == ESD_STREAMING_DATA && !c->sink_input && pa_iochannel_is_hungup(c->io)) /* In case we are in capture mode we will never call read() * on the socket, hence we need to detect the hangup manually * here, instead of simply waiting for read() to return 0. */ @@ -1057,152 +1149,254 @@ static void do_work(struct connection *c) { 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 = 1; + c->dead = TRUE; pa_iochannel_free(c->io); c->io = NULL; - pa_memblockq_prebuf_disable(c->input_memblockq); - pa_sink_notify(c->sink_input->sink); + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL); } else - connection_free(c); + connection_unlink(c); } static void io_callback(pa_iochannel*io, void *userdata) { - struct connection *c = userdata; - assert(io && c && c->io == io); + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(io); do_work(c); } -/*** defer callback ***/ - static void defer_callback(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { - struct connection *c = userdata; - assert(a && c && c->defer_event == e); + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(e); -/* pa_log("DEFER"); */ - 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 ***/ -static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) { - struct connection*c; - assert(i && i->userdata && chunk); - c = i->userdata; - +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + switch (code) { + + case SINK_INPUT_MESSAGE_POST_DATA: { + pa_assert(chunk); + + /* New data from the main loop */ + pa_memblockq_push_align(c->input_memblockq, chunk); + + if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) { + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(c->sink_input, 0, FALSE, TRUE); + } + +/* 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_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(chunk); + if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) { - if (c->dead) - connection_free(c); - + c->playback.underrun = TRUE; + + if (c->dead && pa_sink_input_safe_to_remove(i)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); + return -1; - } + } else { + size_t m; - return 0; + chunk->length = PA_MIN(length, chunk->length); + + c->playback.underrun = FALSE; + + pa_memblockq_drop(c->input_memblockq, chunk->length); + m = pa_memblockq_pop_missing(c->input_memblockq); + + if (m > 0) + if (pa_atomic_add(&c->playback.missing, m) <= 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); + + return 0; + } } -static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { - struct connection*c = i->userdata; - assert(i && c && length); +/* Called from thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + /* If we are in an underrun, then we don't rewind */ + if (i->thread_info.underrun_for > 0) + return; -/* pa_log("DROP"); */ - - pa_memblockq_drop(c->input_memblockq, chunk, length); + pa_memblockq_rewind(c->input_memblockq, nbytes); +} - /* do something */ - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable); +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; - if (!c->dead) - c->protocol->core->mainloop->defer_enable(c->defer_event, 1); + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); -/* assert(pa_memblockq_get_length(c->input_memblockq) > 2048); */ + pa_memblockq_set_maxrewind(c->input_memblockq, nbytes); } static void sink_input_kill_cb(pa_sink_input *i) { - assert(i && i->userdata); - connection_free((struct connection *) i->userdata); -} + pa_sink_input_assert_ref(i); -static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) { - struct connection*c = i->userdata; - assert(i && c); - return pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); + 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) { - struct connection *c = o->userdata; - assert(o && c && chunk); + connection *c; - pa_memblockq_push(c->output_memblockq, chunk); + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + pa_assert(chunk); - /* do something */ - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable); - - if (!c->dead) - c->protocol->core->mainloop->defer_enable(c->defer_event, 1); + 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) { - assert(o && o->userdata); - connection_free((struct connection *) o->userdata); + pa_source_output_assert_ref(o); + + connection_unlink(CONNECTION(o->userdata)); } static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { - struct connection*c = o->userdata; - assert(o && c); + connection*c; + + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); } /*** socket server callback ***/ static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { - struct connection *c = userdata; - assert(m && tv && c && c->auth_timeout_event == e); + 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_free(c); + connection_unlink(c); } static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { - struct connection *c; + connection *c; pa_protocol_esound *p = userdata; char cname[256], pname[128]; - assert(s && io && p); + + 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_xnew(struct connection, 1); + + 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)); - snprintf(cname, sizeof(cname), "EsounD client (%s)", pname); - assert(p->core); + pa_snprintf(cname, sizeof(cname), "EsounD client (%s)", pname); c->client = pa_client_new(p->core, __FILE__, cname); - assert(c->client); - c->client->owner = p->module; + pa_proplist_sets(c->client->proplist, "esound-protocol.peer", pname); + c->client->module = p->module; c->client->kill = client_kill_cb; c->client->userdata = c; - + c->authorized = !!p->public; - c->swap_byte_order = 0; - c->dead = 0; + 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); @@ -1221,17 +1415,17 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) c->playback.current_memblock = NULL; c->playback.memblock_index = 0; - c->playback.fragment_size = 0; + c->playback.underrun = TRUE; + pa_atomic_store(&c->playback.missing, 0); - c->scache.memchunk.length = c->scache.memchunk.index = 0; - c->scache.memchunk.memblock = NULL; + pa_memchunk_reset(&c->scache.memchunk); 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 = 1; + c->authorized = TRUE; } if (!c->authorized) { @@ -1241,9 +1435,8 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) 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); - assert(c->defer_event); p->core->mainloop->defer_enable(c->defer_event, 0); pa_idxset_put(p->connections, c, &c->index); @@ -1252,22 +1445,22 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) /*** 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; - int public = 0; + pa_protocol_esound *p = NULL; + pa_bool_t public = FALSE; const char *acl; - - assert(core); - assert(server); - assert(m); - assert(ma); - p = pa_xnew(pa_protocol_esound, 1); + 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; @@ -1279,14 +1472,13 @@ pa_protocol_esound* pa_protocol_esound_new(pa_core*core, pa_socket_server *serve } } else p->auth_ip_acl = NULL; - + + p->core = core; p->module = m; p->public = public; - p->server = server; + p->server = pa_socket_server_ref(server); pa_socket_server_set_callback(p->server, on_connection, p); - p->core = core; p->connections = pa_idxset_new(NULL, NULL); - assert(p->connections); p->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); p->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL)); @@ -1300,17 +1492,21 @@ fail: } void pa_protocol_esound_free(pa_protocol_esound *p) { - struct connection *c; - assert(p); + connection *c; + pa_assert(p); while ((c = pa_idxset_first(p->connections, NULL))) - connection_free(c); - + connection_unlink(c); pa_idxset_free(p->connections, NULL, NULL); - pa_socket_server_unref(p->server); + + if (p->server) + 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 index 79b5acf0..0c9447d3 100644 --- a/src/pulsecore/protocol-esound.h +++ b/src/pulsecore/protocol-esound.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index 3b1207f6..03990435 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -1,18 +1,18 @@ -/* $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 @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -32,6 +31,7 @@ #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> @@ -63,11 +63,12 @@ struct pa_protocol_http { static void http_response(struct connection *c, int code, const char *msg, const char *mime) { char s[256]; - assert(c); - assert(msg); - assert(mime); - snprintf(s, sizeof(s), + 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" @@ -81,14 +82,14 @@ static void http_response(struct connection *c, int code, const char *msg, const static void http_message(struct connection *c, int code, const char *msg, const char *text) { char s[256]; - assert(c); + pa_assert(c); http_response(c, code, msg, "text/html"); if (!text) text = msg; - snprintf(s, sizeof(s), + 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" @@ -101,21 +102,22 @@ static void http_message(struct connection *c, int code, const char *msg, const static void connection_free(struct connection *c, int del) { - assert(c); + 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; - assert(line); - assert(c); + pa_assert(line); + pa_assert(c); if (!s) { /* EOF */ @@ -137,16 +139,16 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } 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"); @@ -164,22 +166,22 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { #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("Host name:", pa_get_host_name(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); + + 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, + 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" @@ -207,13 +209,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { break; } - + default: ; } return; - + fail: internal_server_error(c); } @@ -221,7 +223,10 @@ fail: static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { pa_protocol_http *p = userdata; struct connection *c; - assert(s && io && p); + + 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); @@ -229,7 +234,7 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) return; } - c = pa_xmalloc(sizeof(struct connection)); + c = pa_xnew(struct connection, 1); c->protocol = p; c->line = pa_ioline_new(io); c->state = REQUEST_LINE; @@ -241,12 +246,14 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) 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; - assert(core && server); - p = pa_xmalloc(sizeof(pa_protocol_http)); + 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->server = pa_socket_server_ref(server); p->connections = pa_idxset_new(NULL, NULL); pa_socket_server_set_callback(p->server, on_connection, p); @@ -255,12 +262,12 @@ pa_protocol_http* pa_protocol_http_new(pa_core *core, pa_socket_server *server, } static void free_connection(void *p, PA_GCC_UNUSED void *userdata) { - assert(p); + pa_assert(p); connection_free(p, 0); } void pa_protocol_http_free(pa_protocol_http *p) { - assert(p); + pa_assert(p); pa_idxset_free(p->connections, free_connection, NULL); pa_socket_server_unref(p->server); diff --git a/src/pulsecore/protocol-http.h b/src/pulsecore/protocol-http.h index 5d5dba31..e3372335 100644 --- a/src/pulsecore/protocol-http.h +++ b/src/pulsecore/protocol-http.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index fba611d7..923a5665 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,7 +26,6 @@ #include <string.h> #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <unistd.h> @@ -58,6 +58,7 @@ #include <pulsecore/creds.h> #include <pulsecore/core-util.h> #include <pulsecore/ipacl.h> +#include <pulsecore/thread-mq.h> #include "protocol-native.h" @@ -68,56 +69,69 @@ #define MAX_CONNECTIONS 64 #define MAX_MEMBLOCKQ_LENGTH (4*1024*1024) /* 4MB */ +#define DEFAULT_TLENGTH_MSEC 2000 /* 2s */ +#define DEFAULT_PROCESS_MSEC 20 /* 20ms */ +#define DEFAULT_FRAGSIZE_MSEC DEFAULT_TLENGTH_MSEC -struct connection; +typedef struct connection connection; struct pa_protocol_native; -struct record_stream { - struct connection *connection; +typedef struct record_stream { + pa_msgobject parent; + + connection *connection; uint32_t index; + pa_source_output *source_output; pa_memblockq *memblockq; size_t fragment_size; -}; + pa_usec_t source_latency; +} record_stream; + +typedef struct output_stream { + pa_msgobject parent; +} output_stream; -struct playback_stream { - int type; - struct connection *connection; +typedef struct playback_stream { + output_stream parent; + + connection *connection; uint32_t index; + pa_sink_input *sink_input; pa_memblockq *memblockq; - size_t requested_bytes; - int drain_request; + pa_bool_t drain_request; uint32_t drain_tag; uint32_t syncid; - int underrun; - /* Sync group members */ - PA_LLIST_FIELDS(struct playback_stream); -}; + pa_atomic_t missing; + size_t minreq; + pa_usec_t sink_latency; + + /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */ + int64_t read_index, write_index; + size_t render_memblockq_length; +} playback_stream; -struct upload_stream { - int type; - struct connection *connection; +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; -}; - -struct output_stream { - int type; -}; - -enum { - UPLOAD_STREAM, - PLAYBACK_STREAM -}; + pa_proplist *proplist; +} upload_stream; struct connection { - int authorized; + pa_msgobject parent; + + pa_bool_t authorized:1; + pa_bool_t is_local:1; uint32_t version; pa_protocol_native *protocol; pa_client *client; @@ -129,31 +143,86 @@ struct connection { 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; - int public; pa_core *core; + pa_bool_t public; pa_socket_server *server; pa_idxset *connections; uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; - int auth_cookie_in_property; + pa_bool_t auth_cookie_in_property; #ifdef HAVE_CREDS char *auth_group; #endif pa_ip_acl *auth_ip_acl; }; -static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk); -static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length); +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, + PLAYBACK_STREAM_MESSAGE_STARTED +}; + +enum { + RECORD_STREAM_MESSAGE_POST_DATA /* data from source output to main loop */ +}; + +enum { + CONNECTION_MESSAGE_RELEASE, + CONNECTION_MESSAGE_REVOKE +}; + +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); static void sink_input_kill_cb(pa_sink_input *i); -static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i); +static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend); +static void sink_input_moved_cb(pa_sink_input *i); +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes); + +static void 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); @@ -176,8 +245,7 @@ static void command_subscribe(pa_pdispatch *pd, uint32_t command, uint32_t tag, 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_flush_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); -static void command_trigger_or_prebuf_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); @@ -190,6 +258,11 @@ static void command_get_autoload_info_list(pa_pdispatch *pd, uint32_t command, u 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 void command_update_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_remove_proplist(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, @@ -234,21 +307,25 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [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_flush_playback_stream, - [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = command_trigger_or_prebuf_playback_stream, - [PA_COMMAND_PREBUF_PLAYBACK_STREAM] = command_trigger_or_prebuf_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_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, @@ -261,140 +338,535 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [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_MOVE_SOURCE_OUTPUT] = command_move_stream, + + [PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr, + [PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr, + + [PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate, + [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate, + + [PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST] = command_update_proplist, + [PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST] = command_update_proplist, + [PA_COMMAND_UPDATE_CLIENT_PROPLIST] = command_update_proplist, + + [PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST] = command_remove_proplist, + [PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST] = command_remove_proplist, + [PA_COMMAND_REMOVE_CLIENT_PROPLIST] = command_remove_proplist, }; /* structure management */ -static struct upload_stream* upload_stream_new( - struct connection *c, - const pa_sample_spec *ss, - const pa_channel_map *map, - const char *name, size_t length) { - - struct upload_stream *s; - assert(c && ss && name && length); - - s = pa_xnew(struct upload_stream, 1); - s->type = UPLOAD_STREAM; +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->proplist) + pa_proplist_free(s->proplist); + + 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, + pa_proplist *p) { + + upload_stream *s; + + pa_assert(c); + pa_assert(ss); + pa_assert(name); + pa_assert(length > 0); + pa_assert(p); + + s = pa_msgobject_new(upload_stream); + s->parent.parent.parent.free = upload_stream_free; s->connection = c; s->sample_spec = *ss; s->channel_map = *map; s->name = pa_xstrdup(name); - - s->memchunk.memblock = NULL; - s->memchunk.index = 0; - s->memchunk.length = 0; - + pa_memchunk_reset(&s->memchunk); s->length = length; - + s->proplist = pa_proplist_copy(p); + pa_proplist_update(s->proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_idxset_put(c->output_streams, s, &s->index); + return s; } -static void upload_stream_free(struct upload_stream *o) { - assert(o && o->connection); +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 void fix_record_buffer_attr_pre(record_stream *s, pa_bool_t adjust_latency, uint32_t *maxlength, uint32_t *fragsize) { + pa_assert(s); + pa_assert(maxlength); + pa_assert(fragsize); + + if (*maxlength <= 0 || *maxlength > MAX_MEMBLOCKQ_LENGTH) + *maxlength = MAX_MEMBLOCKQ_LENGTH; + + if (*fragsize <= 0) + *fragsize = pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec); + + if (adjust_latency) { + pa_usec_t fragsize_usec; + + /* So, the user asked us to adjust the latency according to + * what the source can provide. Half the latency will be + * spent on the hw buffer, half of it in the async buffer + * queue we maintain for each client. */ + + fragsize_usec = pa_bytes_to_usec(*fragsize, &s->source_output->sample_spec); + + s->source_latency = pa_source_output_set_requested_latency(s->source_output, fragsize_usec/2); + + if (fragsize_usec >= s->source_latency*2) + fragsize_usec -= s->source_latency; + else + fragsize_usec = s->source_latency; + + *fragsize = pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec); + } else + s->source_latency = 0; +} + +static void fix_record_buffer_attr_post(record_stream *s, uint32_t *maxlength, uint32_t *fragsize) { + size_t base; + + pa_assert(s); + pa_assert(maxlength); + pa_assert(fragsize); + + *maxlength = pa_memblockq_get_maxlength(s->memblockq); + + base = pa_frame_size(&s->source_output->sample_spec); + + s->fragment_size = (*fragsize/base)*base; + if (s->fragment_size <= 0) + s->fragment_size = base; - pa_idxset_remove_by_data(o->connection->output_streams, o, NULL); + if (s->fragment_size > *maxlength) + s->fragment_size = *maxlength; - pa_xfree(o->name); - - if (o->memchunk.memblock) - pa_memblock_unref(o->memchunk.memblock); - - pa_xfree(o); + *fragsize = s->fragment_size; } -static struct record_stream* record_stream_new( - struct connection *c, - pa_source *source, - const pa_sample_spec *ss, - const pa_channel_map *map, - const char *name, - size_t maxlength, - size_t fragment_size) { - - struct record_stream *s; +static record_stream* record_stream_new( + connection *c, + pa_source *source, + pa_sample_spec *ss, + pa_channel_map *map, + pa_bool_t peak_detect, + uint32_t *maxlength, + uint32_t *fragsize, + pa_source_output_flags_t flags, + pa_proplist *p, + pa_bool_t adjust_latency, + pa_sink_input *direct_on_input) { + + record_stream *s; pa_source_output *source_output; size_t base; pa_source_output_new_data data; - - assert(c && ss && name && maxlength); + + pa_assert(c); + pa_assert(ss); + pa_assert(maxlength); + pa_assert(p); pa_source_output_new_data_init(&data); - data.source = source; + + pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p); + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); 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); data.module = c->protocol->module; data.client = c->client; - - if (!(source_output = pa_source_output_new(c->protocol->core, &data, 0))) + data.source = source; + data.direct_on_input = direct_on_input; + pa_source_output_new_data_set_sample_spec(&data, ss); + pa_source_output_new_data_set_channel_map(&data, map); + if (peak_detect) + data.resample_method = PA_RESAMPLER_PEAKS; + + source_output = pa_source_output_new(c->protocol->core, &data, flags); + + pa_source_output_new_data_done(&data); + + if (!source_output) return NULL; - s = pa_xnew(struct record_stream, 1); + 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; + fix_record_buffer_attr_pre(s, adjust_latency, maxlength, fragsize); + s->memblockq = pa_memblockq_new( 0, - maxlength, + *maxlength, 0, - base = pa_frame_size(ss), + base = pa_frame_size(&source_output->sample_spec), 1, 0, + 0, NULL); - assert(s->memblockq); - s->fragment_size = (fragment_size/base)*base; - if (!s->fragment_size) - s->fragment_size = base; + fix_record_buffer_attr_post(s, maxlength, fragsize); + + *ss = s->source_output->sample_spec; + *map = s->source_output->channel_map; pa_idxset_put(c->record_streams, s, &s->index); + + pa_log_info("Final latency %0.2f ms = %0.2f ms + %0.2f ms", + ((double) pa_bytes_to_usec(s->fragment_size, &source_output->sample_spec) + (double) s->source_latency) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(s->fragment_size, &source_output->sample_spec) / PA_USEC_PER_MSEC, + (double) s->source_latency / PA_USEC_PER_MSEC); + + pa_source_output_put(s->source_output); return s; } -static void record_stream_free(struct record_stream* r) { - assert(r && r->connection); +static void playback_stream_unlink(playback_stream *s) { + pa_assert(s); + + if (!s->connection) + return; - pa_idxset_remove_by_data(r->connection->record_streams, r, NULL); - pa_source_output_disconnect(r->source_output); - pa_source_output_unref(r->source_output); - pa_memblockq_free(r->memblockq); - pa_xfree(r); + 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 struct playback_stream* playback_stream_new( - struct connection *c, +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 (;;) { + if ((l = pa_atomic_load(&s->missing)) <= 0) + break; + + if (pa_atomic_cmpxchg(&s->missing, l, 0)) + break; + } + + if (l <= 0) + break; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_REQUEST); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, l); + pa_pstream_send_tagstruct(s->connection->pstream, t); + +/* pa_log("Requesting %lu bytes", (unsigned long) 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_STARTED: + + if (s->connection->version >= 13) { + pa_tagstruct *t; + + /* Notify the user we're overflowed*/ + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_STARTED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_pstream_send_tagstruct(s->connection->pstream, t); + } + + break; + + case PLAYBACK_STREAM_MESSAGE_DRAIN_ACK: + pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata)); + break; + } + + return 0; +} + +static void fix_playback_buffer_attr_pre(playback_stream *s, pa_bool_t adjust_latency, uint32_t *maxlength, uint32_t *tlength, uint32_t* prebuf, uint32_t* minreq) { + size_t frame_size; + pa_usec_t tlength_usec, minreq_usec, sink_usec; + + pa_assert(s); + pa_assert(maxlength); + pa_assert(tlength); + pa_assert(prebuf); + pa_assert(minreq); + + if (*maxlength <= 0 || *maxlength > MAX_MEMBLOCKQ_LENGTH) + *maxlength = MAX_MEMBLOCKQ_LENGTH; + if (*tlength <= 0) + *tlength = pa_usec_to_bytes(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); + if (*minreq <= 0) + *minreq = pa_usec_to_bytes(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); + + frame_size = pa_frame_size(&s->sink_input->sample_spec); + if (*minreq <= 0) + *minreq = frame_size; + if (*tlength < *minreq+frame_size) + *tlength = *minreq+frame_size; + + tlength_usec = pa_bytes_to_usec(*tlength, &s->sink_input->sample_spec); + minreq_usec = pa_bytes_to_usec(*minreq, &s->sink_input->sample_spec); + + pa_log_info("Requested tlength=%0.2f ms, minreq=%0.2f ms", + (double) tlength_usec / PA_USEC_PER_MSEC, + (double) minreq_usec / PA_USEC_PER_MSEC); + + if (adjust_latency) { + + /* So, the user asked us to adjust the latency of the stream + * buffer according to the what the sink can provide. The + * tlength passed in shall be the overall latency. Roughly + * half the latency will be spent on the hw buffer, the other + * half of it in the async buffer queue we maintain for each + * client. In between we'll have a safety space of size + * 2*minreq. Why the 2*minreq? When the hw buffer is completey + * empty and needs to be filled, then our buffer must have + * enough data to fulfill this request immediatly and thus + * have at least the same tlength as the size of the hw + * buffer. It additionally needs space for 2 times minreq + * because if the buffer ran empty and a partial fillup + * happens immediately on the next iteration we need to be + * able to fulfill it and give the application also minreq + * time to fill it up again for the next request Makes 2 times + * minreq in plus.. */ + + if (tlength_usec > minreq_usec*2) + sink_usec = (tlength_usec - minreq_usec*2)/2; + else + sink_usec = 0; + + } else { + + /* Ok, the user didn't ask us to adjust the latency, but we + * still need to make sure that the parameters from the user + * do make sense. */ + + if (tlength_usec > minreq_usec*2) + sink_usec = (tlength_usec - minreq_usec*2); + else + sink_usec = 0; + } + + s->sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec); + + if (adjust_latency) { + /* Ok, we didn't necessarily get what we were asking for, so + * let's subtract from what we asked for for the remaining + * buffer space */ + + if (tlength_usec >= s->sink_latency) + tlength_usec -= s->sink_latency; + } + + if (tlength_usec < s->sink_latency + 2*minreq_usec) + tlength_usec = s->sink_latency + 2*minreq_usec; + + *tlength = pa_usec_to_bytes(tlength_usec, &s->sink_input->sample_spec); + *minreq = pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec); + + if (*minreq <= 0) { + *minreq += frame_size; + *tlength += frame_size*2; + } + + if (*tlength <= *minreq) + *tlength = *minreq*2 + frame_size; + + if (*prebuf <= 0 || *prebuf > *tlength) + *prebuf = *tlength; +} + +static void fix_playback_buffer_attr_post(playback_stream *s, uint32_t *maxlength, uint32_t *tlength, uint32_t* prebuf, uint32_t* minreq) { + pa_assert(s); + pa_assert(maxlength); + pa_assert(tlength); + pa_assert(prebuf); + pa_assert(minreq); + + *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); + + s->minreq = *minreq; +} + +static playback_stream* playback_stream_new( + connection *c, pa_sink *sink, - const pa_sample_spec *ss, - const pa_channel_map *map, - const char *name, - size_t maxlength, - size_t tlength, - size_t prebuf, - size_t minreq, + pa_sample_spec *ss, + pa_channel_map *map, + uint32_t *maxlength, + uint32_t *tlength, + uint32_t *prebuf, + uint32_t *minreq, pa_cvolume *volume, - uint32_t syncid) { - - struct playback_stream *s, *ssync; + pa_bool_t muted, + uint32_t syncid, + uint32_t *missing, + pa_sink_input_flags_t flags, + pa_proplist *p, + pa_bool_t adjust_latency) { + + playback_stream *s, *ssync; pa_sink_input *sink_input; - pa_memblock *silence; + pa_memchunk silence; uint32_t idx; int64_t start_index; pa_sink_input_new_data data; - - assert(c && ss && name && maxlength); + + pa_assert(c); + pa_assert(ss); + pa_assert(maxlength); + pa_assert(tlength); + pa_assert(prebuf); + pa_assert(minreq); + pa_assert(volume); + pa_assert(missing); + pa_assert(p); /* Find syncid group */ for (ssync = pa_idxset_first(c->output_streams, &idx); ssync; ssync = pa_idxset_next(c->output_streams, &idx)) { - - if (ssync->type != PLAYBACK_STREAM) + + if (!playback_stream_isinstance(ssync)) continue; if (ssync->syncid == syncid) @@ -402,158 +874,190 @@ static struct playback_stream* playback_stream_new( } /* Synced streams must connect to the same sink */ - if (ssync) - sink = ssync->sink_input->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; + + pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p); + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); data.driver = __FILE__; - data.name = name; + data.module = c->protocol->module; + data.client = c->client; + data.sink = sink; 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; + pa_sink_input_new_data_set_muted(&data, muted); + data.sync_base = ssync ? ssync->sink_input : NULL; - if (!(sink_input = pa_sink_input_new(c->protocol->core, &data, 0))) + sink_input = pa_sink_input_new(c->protocol->core, &data, flags); + + pa_sink_input_new_data_done(&data); + + if (!sink_input) return NULL; - - s = pa_xnew(struct playback_stream, 1); - s->type = PLAYBACK_STREAM; + + 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->peek = sink_input_peek_cb; - s->sink_input->drop = sink_input_drop_cb; + + s->sink_input->parent.process_msg = sink_input_process_msg; + s->sink_input->pop = sink_input_pop_cb; + s->sink_input->process_rewind = sink_input_process_rewind_cb; + s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; s->sink_input->kill = sink_input_kill_cb; - s->sink_input->get_latency = sink_input_get_latency_cb; + s->sink_input->moved = sink_input_moved_cb; + s->sink_input->suspend = sink_input_suspend_cb; s->sink_input->userdata = s; - if (ssync) { - /* Sync id found, now find head of list */ - PA_LLIST_FIND_HEAD(struct playback_stream, ssync, &ssync); + start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0; - /* Prepend ourselves */ - PA_LLIST_PREPEND(struct playback_stream, ssync, s); + fix_playback_buffer_attr_pre(s, adjust_latency, maxlength, tlength, prebuf, minreq); + pa_sink_input_get_silence(sink_input, &silence); - /* Set our start index to the current read index of the other grozp member(s) */ - assert(ssync->next); - start_index = pa_memblockq_get_read_index(ssync->next->memblockq); - } else { - /* This ia a new sync group */ - PA_LLIST_INIT(struct playback_stream, s); - start_index = 0; - } - - silence = pa_silence_memblock_new(c->protocol->core->mempool, ss, 0); - s->memblockq = pa_memblockq_new( start_index, - maxlength, - tlength, - pa_frame_size(ss), - prebuf, - minreq, - silence); - - pa_memblock_unref(silence); - - s->requested_bytes = 0; - s->drain_request = 0; - + *maxlength, + *tlength, + pa_frame_size(&sink_input->sample_spec), + *prebuf, + *minreq, + 0, + &silence); + + pa_memblock_unref(silence.memblock); + fix_playback_buffer_attr_post(s, maxlength, tlength, prebuf, minreq); + + *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq); + + *ss = s->sink_input->sample_spec; + *map = s->sink_input->channel_map; + + pa_atomic_store(&s->missing, 0); + s->drain_request = FALSE; + pa_idxset_put(c->output_streams, s, &s->index); + pa_log_info("Final latency %0.2f ms = %0.2f ms + 2*%0.2f ms + %0.2f ms", + ((double) pa_bytes_to_usec(*tlength, &sink_input->sample_spec) + (double) s->sink_latency) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(*tlength-*minreq*2, &sink_input->sample_spec) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(*minreq, &sink_input->sample_spec) / PA_USEC_PER_MSEC, + (double) s->sink_latency / PA_USEC_PER_MSEC); + + pa_sink_input_put(s->sink_input); return s; } -static void playback_stream_free(struct playback_stream* p) { - struct playback_stream *head; - assert(p && p->connection); +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 (p->drain_request) - pa_pstream_send_error(p->connection->pstream, p->drain_tag, PA_ERR_NOENTITY); + if (!c->protocol) + return -1; - PA_LLIST_FIND_HEAD(struct playback_stream, p, &head); - PA_LLIST_REMOVE(struct playback_stream, head, p); + switch (code) { - pa_idxset_remove_by_data(p->connection->output_streams, p, NULL); - pa_sink_input_disconnect(p->sink_input); - pa_sink_input_unref(p->sink_input); - pa_memblockq_free(p->memblockq); - pa_xfree(p); + 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_free(struct connection *c) { - struct record_stream *r; - struct output_stream *o; - assert(c && c->protocol); +static void connection_unlink(connection *c) { + record_stream *r; + output_stream *o; + + pa_assert(c); + + if (!c->protocol) + return; - pa_idxset_remove_by_data(c->protocol->connections, c, NULL); while ((r = pa_idxset_first(c->record_streams, NULL))) - record_stream_free(r); - pa_idxset_free(c->record_streams, NULL, NULL); + record_stream_unlink(r); while ((o = pa_idxset_first(c->output_streams, NULL))) - if (o->type == PLAYBACK_STREAM) - playback_stream_free((struct playback_stream*) o); + if (playback_stream_isinstance(o)) + playback_stream_unlink(PLAYBACK_STREAM(o)); else - upload_stream_free((struct upload_stream*) o); + 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_close(c->pstream); pa_pstream_unref(c->pstream); pa_client_free(c->client); - if (c->subscription) - pa_subscription_free(c->subscription); - - if (c->auth_timeout_event) - c->protocol->core->mainloop->time_free(c->auth_timeout_event); - pa_xfree(c); } -static void request_bytes(struct playback_stream *s) { - pa_tagstruct *t; - size_t l; - assert(s); +/* Called from thread context */ +static void request_bytes(playback_stream *s) { + size_t m, previous_missing; - if (!(l = pa_memblockq_missing(s->memblockq))) - return; - - if (l <= s->requested_bytes) - return; + playback_stream_assert_ref(s); - l -= s->requested_bytes; + m = pa_memblockq_pop_missing(s->memblockq); - if (l < pa_memblockq_get_minreq(s->memblockq)) + if (m <= 0) return; - - s->requested_bytes += l; - t = pa_tagstruct_new(NULL, 0); - assert(t); - 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("request_bytes(%lu)", (unsigned long) m); */ + + previous_missing = pa_atomic_add(&s->missing, m); -/* pa_log("Requesting %u bytes", l); */ + if (pa_memblockq_prebuf_active(s->memblockq) || + (previous_missing < s->minreq && previous_missing+m >= s->minreq)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); } -static void send_memblock(struct connection *c) { +static void send_memblock(connection *c) { uint32_t start; - struct record_stream *r; + record_stream *r; start = PA_IDXSET_INVALID; for (;;) { pa_memchunk chunk; - - if (!(r = pa_idxset_rrobin(c->record_streams, &c->rrobin_index))) + + if (!(r = RECORD_STREAM(pa_idxset_rrobin(c->record_streams, &c->rrobin_index)))) return; if (start == PA_IDXSET_INVALID) @@ -563,22 +1067,23 @@ static void send_memblock(struct connection *c) { if (pa_memblockq_peek(r->memblockq, &chunk) >= 0) { pa_memchunk schunk = chunk; - + if (schunk.length > r->fragment_size) schunk.length = r->fragment_size; pa_pstream_send_memblock(c->pstream, r->index, 0, PA_SEEK_RELATIVE, &schunk); - pa_memblockq_drop(r->memblockq, &chunk, schunk.length); + + pa_memblockq_drop(r->memblockq, schunk.length); pa_memblock_unref(schunk.memblock); - + return; } } } -static void send_playback_stream_killed(struct playback_stream *p) { +static void send_playback_stream_killed(playback_stream *p) { pa_tagstruct *t; - assert(p); + playback_stream_assert_ref(p); t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_KILLED); @@ -587,9 +1092,9 @@ static void send_playback_stream_killed(struct playback_stream *p) { pa_pstream_send_tagstruct(p->connection->pstream, t); } -static void send_record_stream_killed(struct record_stream *r) { +static void send_record_stream_killed(record_stream *r) { pa_tagstruct *t; - assert(r); + record_stream_assert_ref(r); t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_KILLED); @@ -598,107 +1103,418 @@ static void send_record_stream_killed(struct record_stream *r) { pa_pstream_send_tagstruct(r->connection->pstream, t); } -/*** sinkinput callbacks ***/ +/*** sink input callbacks ***/ + +static void handle_seek(playback_stream *s, int64_t indexw) { + playback_stream_assert_ref(s); + +/* pa_log("handle_seek: %llu -- %i", (unsigned long long) s->sink_input->thread_info.underrun_for, pa_memblockq_is_readable(s->memblockq)); */ + + if (s->sink_input->thread_info.underrun_for > 0) { + +/* pa_log("%lu vs. %lu", (unsigned long) pa_memblockq_get_length(s->memblockq), (unsigned long) pa_memblockq_get_prebuf(s->memblockq)); */ -static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) { - struct playback_stream *s; - assert(i && i->userdata && chunk); - s = i->userdata; + if (pa_memblockq_is_readable(s->memblockq)) { - if (pa_memblockq_get_length(s->memblockq) <= 0 && !s->underrun) { - pa_tagstruct *t; + /* We just ended an underrun, let's ask the sink + * for a complete rewind rewrite */ - /* Report that we're empty */ + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(s->sink_input, + s->sink_input->thread_info.underrun_for == (size_t) -1 ? 0 : s->sink_input->thread_info.underrun_for, + FALSE, TRUE); + } + + } else { + int64_t indexr; + + indexr = pa_memblockq_get_read_index(s->memblockq); - 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); + if (indexw < indexr) { + /* OK, the sink already asked for this data, so + * let's have it usk us again */ - s->underrun = 1; + pa_log_debug("Requesting rewind due to rewrite."); + pa_sink_input_request_rewind(s->sink_input, indexr - indexw, TRUE, FALSE); + } } - + + request_bytes(s); +} + +/* 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: { + int64_t windex; + + windex = pa_memblockq_get_write_index(s->memblockq); + pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata)); + + handle_seek(s, windex); + return 0; + } + + case SINK_INPUT_MESSAGE_POST_DATA: { + int64_t windex; + + pa_assert(chunk); + + windex = pa_memblockq_get_write_index(s->memblockq); + +/* pa_log("sink input post: %lu %lli", (unsigned long) chunk->length, (long long) windex); */ + + 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); + } + + handle_seek(s, windex); + +/* pa_log("sink input post2: %lu", (unsigned long) pa_memblockq_get_length(s->memblockq)); */ + + return 0; + } + + case SINK_INPUT_MESSAGE_DRAIN: + case SINK_INPUT_MESSAGE_FLUSH: + case SINK_INPUT_MESSAGE_PREBUF_FORCE: + case SINK_INPUT_MESSAGE_TRIGGER: { + + int64_t windex; + pa_sink_input *isync; + void (*func)(pa_memblockq *bq); + + switch (code) { + case SINK_INPUT_MESSAGE_FLUSH: + func = pa_memblockq_flush; + break; + + case SINK_INPUT_MESSAGE_PREBUF_FORCE: + func = pa_memblockq_prebuf_force; + break; + + case SINK_INPUT_MESSAGE_DRAIN: + case SINK_INPUT_MESSAGE_TRIGGER: + func = pa_memblockq_prebuf_disable; + break; + + default: + pa_assert_not_reached(); + } + + windex = pa_memblockq_get_write_index(s->memblockq); + func(s->memblockq); + handle_seek(s, windex); + + /* Do the same for all other members in the sync group */ + for (isync = i->sync_prev; isync; isync = isync->sync_prev) { + playback_stream *ssync = PLAYBACK_STREAM(isync->userdata); + windex = pa_memblockq_get_write_index(ssync->memblockq); + func(ssync->memblockq); + handle_seek(ssync, windex); + } + + for (isync = i->sync_next; isync; isync = isync->sync_next) { + playback_stream *ssync = PLAYBACK_STREAM(isync->userdata); + windex = pa_memblockq_get_write_index(ssync->memblockq); + func(ssync->memblockq); + handle_seek(ssync, windex); + } + + if (code == SINK_INPUT_MESSAGE_DRAIN) { + if (!pa_memblockq_is_readable(s->memblockq)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, userdata, 0, NULL, NULL); + else { + s->drain_tag = PA_PTR_TO_UINT(userdata); + s->drain_request = TRUE; + } + } + + return 0; + } + + case SINK_INPUT_MESSAGE_UPDATE_LATENCY: + + s->read_index = pa_memblockq_get_read_index(s->memblockq); + s->write_index = pa_memblockq_get_write_index(s->memblockq); + s->render_memblockq_length = pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq); + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_STATE: { + int64_t windex; + + windex = pa_memblockq_get_write_index(s->memblockq); + + pa_memblockq_prebuf_force(s->memblockq); + + handle_seek(s, windex); + + /* Fall through to the default handler */ + break; + } + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &i->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + } + + return pa_sink_input_process_msg(o, code, userdata, offset, chunk); +} + +/* Called from thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + pa_assert(chunk); + if (pa_memblockq_peek(s->memblockq, chunk) < 0) { -/* pa_log("peek: failure"); */ + +/* pa_log("UNDERRUN: %lu", (unsigned long) pa_memblockq_get_length(s->memblockq)); */ + + if (s->drain_request && pa_sink_input_safe_to_remove(i)) { + s->drain_request = FALSE; + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, PA_UINT_TO_PTR(s->drain_tag), 0, NULL, NULL); + } else if (i->thread_info.playing_for > 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UNDERFLOW, NULL, 0, NULL, NULL); + +/* pa_log("adding %llu bytes", (unsigned long long) nbytes); */ + + request_bytes(s); + return -1; } -/* pa_log("peek: %u", chunk->length); */ - +/* pa_log("NOTUNDERRUN %lu", (unsigned long) chunk->length); */ + + chunk->length = PA_MIN(nbytes, chunk->length); + + if (i->thread_info.underrun_for > 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_STARTED, NULL, 0, NULL, NULL); + + pa_memblockq_drop(s->memblockq, chunk->length); + request_bytes(s); + return 0; } -static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { - struct playback_stream *s; - assert(i && i->userdata && length); - s = i->userdata; +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + playback_stream *s; - pa_memblockq_drop(s->memblockq, chunk, length); + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); - request_bytes(s); + /* If we are in an underrun, then we don't rewind */ + if (i->thread_info.underrun_for > 0) + return; - if (s->drain_request && !pa_memblockq_is_readable(s->memblockq)) { - pa_pstream_send_simple_ack(s->connection->pstream, s->drain_tag); - s->drain_request = 0; - } + pa_memblockq_rewind(s->memblockq, nbytes); +} + +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + playback_stream *s; -/* pa_log("after_drop: %u %u", pa_memblockq_get_length(s->memblockq), pa_memblockq_is_readable(s->memblockq)); */ + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + pa_memblockq_set_maxrewind(s->memblockq, nbytes); } +/* Called from main context */ static void sink_input_kill_cb(pa_sink_input *i) { - assert(i && i->userdata); - send_playback_stream_killed((struct playback_stream *) i->userdata); - playback_stream_free((struct playback_stream *) i->userdata); + 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); } -static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) { - struct playback_stream *s; - assert(i && i->userdata); - s = i->userdata; +/* 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); - /*pa_log("get_latency: %u", pa_memblockq_get_length(s->memblockq));*/ - - return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &s->sink_input->sample_spec); + 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; + uint32_t maxlength, tlength, prebuf, minreq; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); + tlength = (uint32_t) pa_memblockq_get_tlength(s->memblockq); + prebuf = (uint32_t) pa_memblockq_get_prebuf(s->memblockq); + minreq = (uint32_t) pa_memblockq_get_minreq(s->memblockq); + + fix_playback_buffer_attr_pre(s, TRUE, &maxlength, &tlength, &prebuf, &minreq); + pa_memblockq_set_maxlength(s->memblockq, maxlength); + pa_memblockq_set_tlength(s->memblockq, tlength); + pa_memblockq_set_prebuf(s->memblockq, prebuf); + pa_memblockq_set_minreq(s->memblockq, minreq); + fix_playback_buffer_attr_post(s, &maxlength, &tlength, &prebuf, &minreq); + + 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); + + if (s->connection->version >= 13) { + pa_tagstruct_putu32(t, maxlength); + pa_tagstruct_putu32(t, tlength); + pa_tagstruct_putu32(t, prebuf); + pa_tagstruct_putu32(t, minreq); + pa_tagstruct_put_usec(t, s->sink_latency); + } + + pa_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) { - struct record_stream *s; - assert(o && o->userdata && chunk); - s = o->userdata; - - if (pa_memblockq_push_align(s->memblockq, chunk) < 0) { - pa_log_warn("Failed to push data into output queue."); - return; - } - - if (!pa_pstream_is_pending(s->connection->pstream)) - send_memblock(s->connection); + 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) { - assert(o && o->userdata); - send_record_stream_killed((struct record_stream *) o->userdata); - record_stream_free((struct record_stream *) o->userdata); + 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) { - struct record_stream *s; - assert(o && o->userdata); - s = o->userdata; + 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; + uint32_t maxlength, fragsize; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + fragsize = (uint32_t) s->fragment_size; + maxlength = (uint32_t) pa_memblockq_get_length(s->memblockq); + + fix_record_buffer_attr_pre(s, TRUE, &maxlength, &fragsize); + pa_memblockq_set_maxlength(s->memblockq, maxlength); + fix_record_buffer_attr_post(s, &maxlength, &fragsize); + + 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_tagstruct_put_boolean(t, pa_source_get_state(o->source) == PA_SOURCE_SUSPENDED); + + if (s->connection->version >= 13) { + pa_tagstruct_putu32(t, maxlength); + pa_tagstruct_putu32(t, fragsize); + pa_tagstruct_put_usec(t, s->source_latency); + } + + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + /*** pdispatch callbacks ***/ -static void protocol_error(struct connection *c) { +static void protocol_error(connection *c) { pa_log("protocol error, kicking client"); - connection_free(c); + connection_unlink(c); } #define CHECK_VALIDITY(pstream, expression, tag, error) do { \ @@ -710,7 +1526,7 @@ if (!(expression)) { \ 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); @@ -718,86 +1534,175 @@ static pa_tagstruct *reply_new(uint32_t tag) { } 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) { - struct connection *c = userdata; - struct playback_stream *s; - uint32_t maxlength, tlength, prebuf, minreq, sink_index, syncid; - const char *name, *sink_name; + connection *c = CONNECTION(userdata); + playback_stream *s; + uint32_t maxlength, tlength, prebuf, minreq, sink_index, syncid, missing; + const char *name = NULL, *sink_name; pa_sample_spec ss; pa_channel_map map; pa_tagstruct *reply; pa_sink *sink = NULL; pa_cvolume volume; - int corked; - - assert(c && t && c->protocol && c->protocol->core); - - 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 || - !pa_tagstruct_eof(t) || - !name) { + pa_bool_t + corked = FALSE, + no_remap = FALSE, + no_remix = FALSE, + fix_format = FALSE, + fix_rate = FALSE, + fix_channels = FALSE, + no_move = FALSE, + variable_rate = FALSE, + muted = FALSE, + adjust_latency = FALSE; + + pa_sink_input_flags_t flags = 0; + pa_proplist *p; + + connection_assert_ref(c); + pa_assert(t); + + if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) || + pa_tagstruct_get( + t, + PA_TAG_SAMPLE_SPEC, &ss, + PA_TAG_CHANNEL_MAP, &map, + PA_TAG_U32, &sink_index, + PA_TAG_STRING, &sink_name, + PA_TAG_U32, &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) { + 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, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(sink_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 && maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID); + + p = pa_proplist_new(); + + if (name) + pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name); + + if (c->version >= 12) { + /* Since 0.9.8 the user can ask for a couple of additional flags */ + + if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || + pa_tagstruct_get_boolean(t, &no_remix) < 0 || + pa_tagstruct_get_boolean(t, &fix_format) < 0 || + pa_tagstruct_get_boolean(t, &fix_rate) < 0 || + pa_tagstruct_get_boolean(t, &fix_channels) < 0 || + pa_tagstruct_get_boolean(t, &no_move) < 0 || + pa_tagstruct_get_boolean(t, &variable_rate) < 0) { + + protocol_error(c); + pa_proplist_free(p); + return; + } + } + + if (c->version >= 13) { + + if (pa_tagstruct_get_boolean(t, &muted) < 0 || + pa_tagstruct_get_boolean(t, &adjust_latency) < 0 || + pa_tagstruct_get_proplist(t, p) < 0) { + protocol_error(c); + pa_proplist_free(p); + return; + } + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } 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); + + if (!(sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + pa_proplist_free(p); + return; + } + } 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); + + if (!(sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK, 1))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + pa_proplist_free(p); + return; + } } - s = playback_stream_new(c, sink, &ss, &map, name, maxlength, tlength, prebuf, minreq, &volume, syncid); + 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, &maxlength, &tlength, &prebuf, &minreq, &volume, muted, syncid, &missing, flags, p, adjust_latency); + pa_proplist_free(p); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID); - pa_sink_input_cork(s->sink_input, corked); - reply = reply_new(tag); pa_tagstruct_putu32(reply, s->index); - assert(s->sink_input); + pa_assert(s->sink_input); pa_tagstruct_putu32(reply, s->sink_input->index); - pa_tagstruct_putu32(reply, s->requested_bytes = pa_memblockq_missing(s->memblockq)); + pa_tagstruct_putu32(reply, missing); + +/* pa_log("initial request is %u", missing); */ if (c->version >= 9) { - /* Since 0.9 we support sending the buffer metrics back to the client */ + /* Since 0.9.0 we support sending the buffer metrics back to the client */ + + pa_tagstruct_putu32(reply, (uint32_t) maxlength); + pa_tagstruct_putu32(reply, (uint32_t) tlength); + pa_tagstruct_putu32(reply, (uint32_t) prebuf); + pa_tagstruct_putu32(reply, (uint32_t) minreq); + } - pa_tagstruct_putu32(reply, (uint32_t) 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)); + 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); } - + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->sink_latency); + pa_pstream_send_tagstruct(c->pstream, reply); - request_bytes(s); } static void command_delete_stream(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t channel; - assert(c && t); - + + connection_assert_ref(c); + pa_assert(t); + if (pa_tagstruct_getu32(t, &channel) < 0 || !pa_tagstruct_eof(t)) { protocol_error(c); @@ -806,119 +1711,234 @@ static void command_delete_stream(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t comma CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - if (command == PA_COMMAND_DELETE_PLAYBACK_STREAM) { - struct playback_stream *s; - if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || (s->type != PLAYBACK_STREAM)) { - pa_pstream_send_error(c->pstream, tag, PA_ERR_EXIST); - return; + 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; } - playback_stream_free(s); - } else if (command == PA_COMMAND_DELETE_RECORD_STREAM) { - struct 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; + 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; } - record_stream_free(s); - } else { - struct upload_stream *s; - assert(command == PA_COMMAND_DELETE_UPLOAD_STREAM); - if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || (s->type != UPLOAD_STREAM)) { - pa_pstream_send_error(c->pstream, tag, PA_ERR_EXIST); - return; + 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; } - upload_stream_free(s); + 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) { - struct connection *c = userdata; - struct record_stream *s; + connection *c = CONNECTION(userdata); + record_stream *s; uint32_t maxlength, fragment_size; uint32_t source_index; - const char *name, *source_name; + const char *name = NULL, *source_name; pa_sample_spec ss; pa_channel_map map; pa_tagstruct *reply; pa_source *source = NULL; - int corked; - assert(c && t && c->protocol && c->protocol->core); - - if (pa_tagstruct_gets(t, &name) < 0 || + pa_bool_t + corked = FALSE, + no_remap = FALSE, + no_remix = FALSE, + fix_format = FALSE, + fix_rate = FALSE, + fix_channels = FALSE, + no_move = FALSE, + variable_rate = FALSE, + adjust_latency = FALSE, + peak_detect = FALSE; + pa_source_output_flags_t flags = 0; + pa_proplist *p; + uint32_t direct_on_input_idx = PA_INVALID_INDEX; + pa_sink_input *direct_on_input = NULL; + + connection_assert_ref(c); + pa_assert(t); + + if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) || pa_tagstruct_get_sample_spec(t, &ss) < 0 || pa_tagstruct_get_channel_map(t, &map) < 0 || pa_tagstruct_getu32(t, &source_index) < 0 || pa_tagstruct_gets(t, &source_name) < 0 || pa_tagstruct_getu32(t, &maxlength) < 0 || pa_tagstruct_get_boolean(t, &corked) < 0 || - pa_tagstruct_getu32(t, &fragment_size) < 0 || - !pa_tagstruct_eof(t)) { + pa_tagstruct_getu32(t, &fragment_size) < 0) { protocol_error(c); return; } CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, 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 <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID); + + p = pa_proplist_new(); + + if (name) + pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name); + + if (c->version >= 12) { + /* Since 0.9.8 the user can ask for a couple of additional flags */ + + if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || + pa_tagstruct_get_boolean(t, &no_remix) < 0 || + pa_tagstruct_get_boolean(t, &fix_format) < 0 || + pa_tagstruct_get_boolean(t, &fix_rate) < 0 || + pa_tagstruct_get_boolean(t, &fix_channels) < 0 || + pa_tagstruct_get_boolean(t, &no_move) < 0 || + pa_tagstruct_get_boolean(t, &variable_rate) < 0) { + + protocol_error(c); + pa_proplist_free(p); + return; + } + } + + if (c->version >= 13) { + + if (pa_tagstruct_get_boolean(t, &peak_detect) < 0 || + pa_tagstruct_get_boolean(t, &adjust_latency) < 0 || + pa_tagstruct_get_proplist(t, p) < 0 || + pa_tagstruct_getu32(t, &direct_on_input_idx) < 0) { + protocol_error(c); + pa_proplist_free(p); + return; + } + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } 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); + + if (!(source = pa_idxset_get_by_index(c->protocol->core->sources, source_index))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + pa_proplist_free(p); + return; + } + } 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); + + if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE, 1))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + pa_proplist_free(p); + return; + } + } + + if (direct_on_input_idx != PA_INVALID_INDEX) { + + if (!(direct_on_input = pa_idxset_get_by_index(c->protocol->core->sink_inputs, direct_on_input_idx))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + pa_proplist_free(p); + 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); + + s = record_stream_new(c, source, &ss, &map, peak_detect, &maxlength, &fragment_size, flags, p, adjust_latency, direct_on_input); + pa_proplist_free(p); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID); - - pa_source_output_cork(s->source_output, corked); - + reply = reply_new(tag); pa_tagstruct_putu32(reply, s->index); - assert(s->source_output); + 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) pa_memblockq_get_maxlength(s->memblockq)); - pa_tagstruct_putu32(reply, (uint32_t) s->fragment_size); + pa_tagstruct_putu32(reply, (uint32_t) maxlength); + pa_tagstruct_putu32(reply, (uint32_t) 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); + } + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->source_latency); + 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) { - struct connection *c = userdata; - assert(c && t); - + 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); - - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop); + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); const void*cookie; pa_tagstruct *reply; - assert(c && t); + pa_bool_t shm_on_remote, do_shm; + + 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 || @@ -933,76 +1953,103 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t return; } + /* Starting with protocol version 13 the MSB of the version tag + reflects if shm is available for this connection or + not. */ + if (c->version >= 13) { + shm_on_remote = !!(c->version & 0x80000000U); + c->version &= 0x7FFFFFFFU; + } + + pa_log_debug("Protocol version: remote %u, local %u", c->version, PA_PROTOCOL_VERSION); + + pa_proplist_setf(c->client->proplist, "native-protocol.version", "%u", c->version); + if (!c->authorized) { - int success = 0; - + pa_bool_t success = FALSE; + #ifdef HAVE_CREDS const pa_creds *creds; if ((creds = pa_pdispatch_creds(pd))) { if (creds->uid == getuid()) - success = 1; + success = TRUE; 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); + pa_log_warn("Failed to get GID of group '%s'", c->protocol->auth_group); else if (gid == creds->gid) - success = 1; - + success = TRUE; + if (!success) { if ((r = pa_uid_in_group(creds->uid, c->protocol->auth_group)) < 0) - pa_log_warn("failed to check group membership."); + pa_log_warn("Failed to check group membership."); else if (r > 0) - success = 1; + success = TRUE; } } - + 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"); - } - + (int) success); } #endif if (!success && memcmp(c->protocol->auth_cookie, cookie, PA_NATIVE_COOKIE_LENGTH) == 0) - success = 1; + success = TRUE; 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; + + c->authorized = TRUE; if (c->auth_timeout_event) { c->protocol->core->mainloop->time_free(c->auth_timeout_event); c->auth_timeout_event = NULL; } } + /* Enable shared memory support if possible */ + do_shm = + pa_mempool_is_shared(c->protocol->core->mempool) && + c->is_local; + + pa_log_debug("SHM possible: %s", pa_yes_no(do_shm)); + + if (do_shm) + if (c->version < 10 || (c->version >= 13 && !shm_on_remote)) + do_shm = FALSE; + + if (do_shm) { + /* Only enable SHM if both sides are owned by the same + * user. This is a security measure because otherwise data + * private to the user might leak. */ + + const pa_creds *creds; + if (!(creds = pa_pdispatch_creds(pd)) || getuid() != creds->uid) + do_shm = FALSE; + } + + pa_log_debug("Negotiated SHM: %s", pa_yes_no(do_shm)); + pa_pstream_enable_shm(c->pstream, do_shm); + reply = reply_new(tag); - pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION); + pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION | (do_shm ? 0x80000000 : 0)); #ifdef HAVE_CREDS { /* SHM support is only enabled after both sides made sure they are the same user. */ - + pa_creds ucred; ucred.uid = getuid(); ucred.gid = getgid(); - + pa_pstream_send_tagstruct_with_creds(c->pstream, reply, &ucred); } #else @@ -1011,27 +2058,52 @@ static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t } 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) { - struct connection *c = userdata; - const char *name; - assert(c && t); + connection *c = CONNECTION(userdata); + const char *name = NULL; + pa_proplist *p; + pa_tagstruct *reply; - if (pa_tagstruct_gets(t, &name) < 0 || + connection_assert_ref(c); + pa_assert(t); + + p = pa_proplist_new(); + + if ((c->version < 13 && pa_tagstruct_gets(t, &name) < 0) || + (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || !pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); return; } - 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); + if (name) + if (pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + pa_proplist_free(p); + return; + } + + pa_proplist_update(c->client->proplist, PA_UPDATE_REPLACE, p); + pa_proplist_free(p); + + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index); + + reply = reply_new(tag); + + if (c->version >= 13) + pa_tagstruct_putu32(reply, c->client->index); + + pa_pstream_send_tagstruct(c->pstream, reply); } static void command_lookup(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); const char *name; uint32_t idx = PA_IDXSET_INVALID; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_gets(t, &name) < 0 || !pa_tagstruct_eof(t)) { @@ -1048,7 +2120,7 @@ static void command_lookup(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uin idx = sink->index; } else { pa_source *source; - assert(command == PA_COMMAND_LOOKUP_SOURCE); + pa_assert(command == PA_COMMAND_LOOKUP_SOURCE); if ((source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1))) idx = source->index; } @@ -1064,10 +2136,12 @@ static void command_lookup(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uin } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; - struct playback_stream *s; - assert(c && t); + playback_stream *s; + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || !pa_tagstruct_eof(t)) { @@ -1078,29 +2152,18 @@ static void command_drain_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC 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, s->type == PLAYBACK_STREAM, tag, PA_ERR_NOENTITY); - - s->drain_request = 0; - - pa_memblockq_prebuf_disable(s->memblockq); - - if (!pa_memblockq_is_readable(s->memblockq)) { -/* pa_log("immediate drain: %u", pa_memblockq_get_length(s->memblockq)); */ - pa_pstream_send_simple_ack(c->pstream, tag); - } else { -/* pa_log("slow drain triggered"); */ - s->drain_request = 1; - s->drain_tag = tag; + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); - pa_sink_notify(s->sink_input->sink); - } -} + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_tagstruct *reply; const pa_mempool_stat *stat; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (!pa_tagstruct_eof(t)) { protocol_error(c); @@ -1110,25 +2173,27 @@ static void command_stat(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t 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) AO_load_acquire_read((AO_t*) &stat->n_allocated)); - pa_tagstruct_putu32(reply, (uint32_t) AO_load_acquire_read((AO_t*) &stat->allocated_size)); - pa_tagstruct_putu32(reply, (uint32_t) AO_load_acquire_read((AO_t*) &stat->n_accumulated)); - pa_tagstruct_putu32(reply, (uint32_t) AO_load_acquire_read((AO_t*) &stat->accumulated_size)); + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_tagstruct *reply; - struct playback_stream *s; + playback_stream *s; struct timeval tv, now; uint32_t idx; pa_usec_t latency; - assert(c && t); - + + 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)) { @@ -1139,31 +2204,40 @@ static void command_get_playback_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_ 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, s->type == PLAYBACK_STREAM, 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); - if (s->sink_input->resampled_chunk.memblock) - latency += pa_bytes_to_usec(s->sink_input->resampled_chunk.length, &s->sink_input->sample_spec); + latency += pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sample_spec); + pa_tagstruct_put_usec(reply, latency); - + pa_tagstruct_put_usec(reply, 0); - pa_tagstruct_put_boolean(reply, s->sink_input->state == PA_SINK_INPUT_RUNNING); + pa_tagstruct_put_boolean(reply, s->sink_input->thread_info.playing_for > 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_tagstruct_puts64(reply, s->write_index); + pa_tagstruct_puts64(reply, s->read_index); + + if (c->version >= 13) { + pa_tagstruct_putu64(reply, s->sink_input->thread_info.underrun_for); + pa_tagstruct_putu64(reply, s->sink_input->thread_info.playing_for); + } + pa_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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_tagstruct *reply; - struct record_stream *s; + record_stream *s; struct timeval tv, now; uint32_t idx; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || pa_tagstruct_get_timeval(t, &tv) < 0 || @@ -1179,7 +2253,7 @@ static void command_get_record_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UN 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_boolean(reply, pa_source_get_state(s->source_output->source) == PA_SOURCE_RUNNING); pa_tagstruct_put_timeval(reply, &tv); pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now)); pa_tagstruct_puts64(reply, pa_memblockq_get_write_index(s->memblockq)); @@ -1188,20 +2262,22 @@ static void command_get_record_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UN } 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) { - struct connection *c = userdata; - struct upload_stream *s; + connection *c = CONNECTION(userdata); + upload_stream *s; uint32_t length; - const char *name; + const char *name = NULL; pa_sample_spec ss; pa_channel_map map; pa_tagstruct *reply; - assert(c && t && c->protocol && c->protocol->core); - - if (pa_tagstruct_gets(t, &name) < 0 || + pa_proplist *p; + + 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)) { + pa_tagstruct_getu32(t, &length) < 0) { protocol_error(c); return; } @@ -1212,11 +2288,28 @@ static void command_create_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_ CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, (length % pa_frame_size(&ss)) == 0 && length > 0, tag, PA_ERR_INVALID); CHECK_VALIDITY(c->pstream, length <= PA_SCACHE_ENTRY_SIZE_MAX, tag, PA_ERR_TOOLARGE); + + p = pa_proplist_new(); + + if (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) { + protocol_error(c); + pa_proplist_free(p); + return; + } + + if (c->version < 13) + pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name); + else if (!name) + if (!(name = pa_proplist_gets(p, PA_PROP_EVENT_ID))) + name = pa_proplist_gets(p, PA_PROP_MEDIA_NAME); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID); - - s = upload_stream_new(c, &ss, &map, name, length); + + s = upload_stream_new(c, &ss, &map, name, length, p); + pa_proplist_free(p); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID); - + reply = reply_new(tag); pa_tagstruct_putu32(reply, s->index); pa_tagstruct_putu32(reply, length); @@ -1224,12 +2317,14 @@ static void command_create_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_ } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t channel; - struct upload_stream *s; + upload_stream *s; uint32_t idx; - assert(c && t); - + + connection_assert_ref(c); + pa_assert(t); + if (pa_tagstruct_getu32(t, &channel) < 0 || !pa_tagstruct_eof(t)) { protocol_error(c); @@ -1240,34 +2335,39 @@ static void command_finish_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_ s = pa_idxset_get_by_index(c->output_streams, channel); CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); - CHECK_VALIDITY(c->pstream, s->type == UPLOAD_STREAM, 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) + if (pa_scache_add_item(c->protocol->core, s->name, &s->sample_spec, &s->channel_map, &s->memchunk, s->proplist, &idx) < 0) pa_pstream_send_error(c->pstream, tag, PA_ERR_INTERNAL); else pa_pstream_send_simple_ack(c->pstream, tag); - - upload_stream_free(s); + + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t sink_index; pa_volume_t volume; pa_sink *sink; const char *name, *sink_name; - assert(c && t); + uint32_t idx; + pa_proplist *p; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); if (pa_tagstruct_getu32(t, &sink_index) < 0 || pa_tagstruct_gets(t, &sink_name) < 0 || pa_tagstruct_getu32(t, &volume) < 0 || - pa_tagstruct_gets(t, &name) < 0 || - !pa_tagstruct_eof(t)) { + pa_tagstruct_gets(t, &name) < 0) { 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); @@ -1278,18 +2378,37 @@ static void command_play_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED ui CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); - if (pa_scache_play_item(c->protocol->core, name, sink, volume) < 0) { + p = pa_proplist_new(); + + if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } + + if (pa_scache_play_item(c->protocol->core, name, sink, volume, p, &idx) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + pa_proplist_free(p); return; } - pa_pstream_send_simple_ack(c->pstream, tag); + pa_proplist_free(p); + + reply = reply_new(tag); + + if (c->version >= 13) + pa_tagstruct_putu32(reply, idx); + + pa_pstream_send_tagstruct(c->pstream, reply); } static void command_remove_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); const char *name; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_gets(t, &name) < 0 || !pa_tagstruct_eof(t)) { @@ -1308,62 +2427,105 @@ static void command_remove_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED pa_pstream_send_simple_ack(c->pstream, tag); } -static void sink_fill_tagstruct(pa_tagstruct *t, pa_sink *sink) { - assert(t && sink); +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, &sink->sample_spec, + PA_TAG_STRING, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), + PA_TAG_SAMPLE_SPEC, &fixed_ss, PA_TAG_CHANNEL_MAP, &sink->channel_map, - PA_TAG_U32, sink->owner ? sink->owner->index : PA_INVALID_INDEX, - PA_TAG_CVOLUME, pa_sink_get_volume(sink, PA_MIXER_HARDWARE), - PA_TAG_BOOLEAN, pa_sink_get_mute(sink, PA_MIXER_HARDWARE), + 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->get_hw_volume ? PA_SINK_HW_VOLUME_CTRL : 0) | - (sink->get_latency ? PA_SINK_LATENCY : 0) | - (sink->is_hardware ? PA_SINK_HARDWARE : 0), + PA_TAG_U32, sink->flags, PA_TAG_INVALID); + + if (c->version >= 13) { + pa_tagstruct_put_proplist(t, sink->proplist); + pa_tagstruct_put_usec(t, pa_sink_get_requested_latency(sink)); + } } -static void source_fill_tagstruct(pa_tagstruct *t, pa_source *source) { - assert(t && source); +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, &source->sample_spec, + PA_TAG_STRING, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), + PA_TAG_SAMPLE_SPEC, &fixed_ss, PA_TAG_CHANNEL_MAP, &source->channel_map, - PA_TAG_U32, source->owner ? source->owner->index : PA_INVALID_INDEX, - PA_TAG_CVOLUME, pa_source_get_volume(source, PA_MIXER_HARDWARE), - PA_TAG_BOOLEAN, pa_source_get_mute(source, PA_MIXER_HARDWARE), + 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->get_hw_volume ? PA_SOURCE_HW_VOLUME_CTRL : 0) | - (source->get_latency ? PA_SOURCE_LATENCY : 0) | - (source->is_hardware ? PA_SOURCE_HARDWARE : 0), + PA_TAG_U32, source->flags, PA_TAG_INVALID); + + if (c->version >= 13) { + pa_tagstruct_put_proplist(t, source->proplist); + pa_tagstruct_put_usec(t, pa_source_get_requested_latency(source)); + } } -static void client_fill_tagstruct(pa_tagstruct *t, pa_client *client) { - assert(t && client); + +static void client_fill_tagstruct(connection *c, 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, pa_strnull(pa_proplist_gets(client->proplist, PA_PROP_APPLICATION_NAME))); + pa_tagstruct_putu32(t, client->module ? client->module->index : PA_INVALID_INDEX); pa_tagstruct_puts(t, client->driver); + + if (c->version >= 13) + pa_tagstruct_put_proplist(t, client->proplist); + } static void module_fill_tagstruct(pa_tagstruct *t, pa_module *module) { - assert(t && 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); @@ -1371,52 +2533,85 @@ static void module_fill_tagstruct(pa_tagstruct *t, pa_module *module) { pa_tagstruct_put_boolean(t, module->auto_unload); } -static void sink_input_fill_tagstruct(pa_tagstruct *t, pa_sink_input *s) { - assert(t && s); +static void sink_input_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink_input *s) { + pa_sample_spec fixed_ss; + pa_usec_t sink_latency; + + 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_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME))); pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX); pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX); pa_tagstruct_putu32(t, s->sink->index); - pa_tagstruct_put_sample_spec(t, &s->sample_spec); + 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_put_usec(t, pa_sink_input_get_latency(s, &sink_latency)); + pa_tagstruct_put_usec(t, sink_latency); pa_tagstruct_puts(t, pa_resample_method_to_string(pa_sink_input_get_resample_method(s))); pa_tagstruct_puts(t, s->driver); + if (c->version >= 11) + pa_tagstruct_put_boolean(t, pa_sink_input_get_mute(s)); + if (c->version >= 13) + pa_tagstruct_put_proplist(t, s->proplist); } -static void source_output_fill_tagstruct(pa_tagstruct *t, pa_source_output *s) { - assert(t && s); +static void source_output_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source_output *s) { + pa_sample_spec fixed_ss; + pa_usec_t source_latency; + + 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_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME))); pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX); pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX); pa_tagstruct_putu32(t, s->source->index); - pa_tagstruct_put_sample_spec(t, &s->sample_spec); + 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_put_usec(t, pa_source_output_get_latency(s, &source_latency)); + pa_tagstruct_put_usec(t, source_latency); pa_tagstruct_puts(t, pa_resample_method_to_string(pa_source_output_get_resample_method(s))); pa_tagstruct_puts(t, s->driver); + + if (c->version >= 13) + pa_tagstruct_put_proplist(t, s->proplist); } -static void scache_fill_tagstruct(pa_tagstruct *t, pa_scache_entry *e) { - assert(t && e); +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); + + if (e->memchunk.memblock) + fixup_sample_spec(c, &fixed_ss, &e->sample_spec); + else + memset(&fixed_ss, 0, sizeof(fixed_ss)); + pa_tagstruct_putu32(t, e->index); pa_tagstruct_puts(t, e->name); 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, &e->sample_spec); + pa_tagstruct_put_usec(t, e->memchunk.memblock ? pa_bytes_to_usec(e->memchunk.length, &e->sample_spec) : 0); + pa_tagstruct_put_sample_spec(t, &fixed_ss); pa_tagstruct_put_channel_map(t, &e->channel_map); pa_tagstruct_putu32(t, e->memchunk.length); pa_tagstruct_put_boolean(t, e->lazy); pa_tagstruct_puts(t, e->filename); + + if (c->version >= 13) + pa_tagstruct_put_proplist(t, e->proplist); } static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; pa_sink *sink = NULL; pa_source *source = NULL; @@ -1427,7 +2622,9 @@ static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, u pa_scache_entry *sce = NULL; const char *name; pa_tagstruct *reply; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || (command != PA_COMMAND_GET_CLIENT_INFO && @@ -1455,20 +2652,20 @@ static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, u 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) + 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 { - assert(command == PA_COMMAND_GET_SAMPLE_INFO); + 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; @@ -1476,29 +2673,31 @@ static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, u reply = reply_new(tag); if (sink) - sink_fill_tagstruct(reply, sink); + sink_fill_tagstruct(c, reply, sink); else if (source) - source_fill_tagstruct(reply, source); + source_fill_tagstruct(c, reply, source); else if (client) - client_fill_tagstruct(reply, client); + client_fill_tagstruct(c, reply, client); else if (module) module_fill_tagstruct(reply, module); else if (si) - sink_input_fill_tagstruct(reply, si); + sink_input_fill_tagstruct(c, reply, si); else if (so) - source_output_fill_tagstruct(reply, so); + source_output_fill_tagstruct(c, reply, so); else - scache_fill_tagstruct(reply, sce); + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_idxset *i; uint32_t idx; void *p; pa_tagstruct *reply; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (!pa_tagstruct_eof(t)) { protocol_error(c); @@ -1522,54 +2721,59 @@ static void command_get_info_list(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t comma else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST) i = c->protocol->core->source_outputs; else { - assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST); + 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(reply, p); + sink_fill_tagstruct(c, reply, p); else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST) - source_fill_tagstruct(reply, p); + source_fill_tagstruct(c, reply, p); else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST) - client_fill_tagstruct(reply, p); + client_fill_tagstruct(c, 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(reply, p); - else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST) - source_output_fill_tagstruct(reply, p); + 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 { - assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST); - scache_fill_tagstruct(reply, p); + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_tagstruct *reply; char txt[256]; const char *n; - assert(c && t); + 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))); - pa_tagstruct_put_sample_spec(reply, &c->protocol->core->default_sample_spec); + pa_tagstruct_puts(reply, pa_get_host_name(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); @@ -1577,14 +2781,15 @@ static void command_get_server_info(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSE 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; - struct connection *c = userdata; - assert(c && core); + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE_EVENT); @@ -1595,9 +2800,11 @@ static void subscription_cb(pa_core *core, pa_subscription_event_type_t e, uint3 } static void command_subscribe(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_subscription_mask_t m; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &m) < 0 || !pa_tagstruct_eof(t)) { @@ -1607,13 +2814,13 @@ static void command_subscribe(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint 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); - assert(c->subscription); + pa_assert(c->subscription); } else c->subscription = NULL; @@ -1626,15 +2833,17 @@ static void command_set_volume( uint32_t tag, pa_tagstruct *t, void *userdata) { - - struct connection *c = 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; - assert(c && t); + + 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) || @@ -1644,32 +2853,41 @@ static void command_set_volume( 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); - if (command == 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); - } else if (command == PA_COMMAND_SET_SOURCE_VOLUME) { - if (idx != (uint32_t) -1) - 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 { - assert(command == PA_COMMAND_SET_SINK_INPUT_VOLUME); - si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + 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, PA_MIXER_HARDWARE, &volume); + pa_sink_set_volume(sink, &volume); else if (source) - pa_source_set_volume(source, PA_MIXER_HARDWARE, &volume); + pa_source_set_volume(source, &volume); else if (si) pa_sink_input_set_volume(si, &volume); @@ -1682,55 +2900,77 @@ static void command_set_mute( uint32_t tag, pa_tagstruct *t, void *userdata) { - - struct connection *c = userdata; + + connection *c = CONNECTION(userdata); uint32_t idx; - int mute; + pa_bool_t mute; pa_sink *sink = NULL; pa_source *source = NULL; + pa_sink_input *si = NULL; const char *name = NULL; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || - pa_tagstruct_gets(t, &name) < 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); - if (command == 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); - } else { - assert(command == PA_COMMAND_SET_SOURCE_MUTE); - if (idx != (uint32_t) -1) - source = pa_idxset_get_by_index(c->protocol->core->sources, idx); - else - source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1); + 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, sink || source, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY); if (sink) - pa_sink_set_mute(sink, PA_MIXER_HARDWARE, mute); + pa_sink_set_mute(sink, mute); else if (source) - pa_source_set_mute(source, PA_MIXER_HARDWARE, mute); + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; - int b; - struct playback_stream *s, *ssync; - assert(c && t); + pa_bool_t 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 || @@ -1743,73 +2983,19 @@ static void command_cork_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_ 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, s->type == PLAYBACK_STREAM, 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_memblockq_prebuf_force(s->memblockq); - - /* Do the same for all other members in the sync group */ - for (ssync = s->prev; ssync; ssync = ssync->prev) { - pa_sink_input_cork(ssync->sink_input, b); - pa_memblockq_prebuf_force(ssync->memblockq); - } - - for (ssync = s->next; ssync; ssync = ssync->next) { - pa_sink_input_cork(ssync->sink_input, b); - pa_memblockq_prebuf_force(ssync->memblockq); - } - - pa_pstream_send_simple_ack(c->pstream, tag); -} - -static void command_flush_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; - uint32_t idx; - struct playback_stream *s, *ssync; - assert(c && 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, s->type == PLAYBACK_STREAM, tag, PA_ERR_NOENTITY); - - pa_memblockq_flush(s->memblockq); - s->underrun = 0; - - /* Do the same for all other members in the sync group */ - for (ssync = s->prev; ssync; ssync = ssync->prev) { - pa_memblockq_flush(ssync->memblockq); - ssync->underrun = 0; - } - - for (ssync = s->next; ssync; ssync = ssync->next) { - pa_memblockq_flush(ssync->memblockq); - ssync->underrun = 0; - } - pa_pstream_send_simple_ack(c->pstream, tag); - pa_sink_notify(s->sink_input->sink); - request_bytes(s); - - for (ssync = s->prev; ssync; ssync = ssync->prev) - request_bytes(ssync); - - for (ssync = s->next; ssync; ssync = ssync->next) - request_bytes(ssync); } -static void command_trigger_or_prebuf_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; +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; - struct playback_stream *s; - assert(c && t); + playback_stream *s; + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || !pa_tagstruct_eof(t)) { @@ -1821,32 +3007,36 @@ static void command_trigger_or_prebuf_playback_stream(PA_GCC_UNUSED pa_pdispatch 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, s->type == PLAYBACK_STREAM, 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_memblockq_prebuf_force(s->memblockq); + 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_memblockq_prebuf_disable(s->memblockq); + pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_TRIGGER, NULL, 0, NULL); break; - + default: - abort(); + pa_assert_not_reached(); } - pa_sink_notify(s->sink_input->sink); pa_pstream_send_simple_ack(c->pstream, tag); - request_bytes(s); } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; - struct record_stream *s; - int b; - assert(c && t); + record_stream *s; + pa_bool_t b; + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || pa_tagstruct_get_boolean(t, &b) < 0 || @@ -1865,10 +3055,12 @@ static void command_cork_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UN } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; - struct record_stream *s; - assert(c && t); + record_stream *s; + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || !pa_tagstruct_eof(t)) { @@ -1884,10 +3076,301 @@ static void command_flush_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_U 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; + pa_bool_t adjust_latency = FALSE; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + if (pa_tagstruct_get( + t, + PA_TAG_U32, &maxlength, + PA_TAG_U32, &tlength, + PA_TAG_U32, &prebuf, + PA_TAG_U32, &minreq, + PA_TAG_INVALID) < 0 || + (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + fix_playback_buffer_attr_pre(s, adjust_latency, &maxlength, &tlength, &prebuf, &minreq); + pa_memblockq_set_maxlength(s->memblockq, maxlength); + pa_memblockq_set_tlength(s->memblockq, tlength); + pa_memblockq_set_prebuf(s->memblockq, prebuf); + pa_memblockq_set_minreq(s->memblockq, minreq); + fix_playback_buffer_attr_post(s, &maxlength, &tlength, &prebuf, &minreq); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, maxlength); + pa_tagstruct_putu32(reply, tlength); + pa_tagstruct_putu32(reply, prebuf); + pa_tagstruct_putu32(reply, minreq); + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->sink_latency); + + } else { + record_stream *s; + pa_bool_t adjust_latency = FALSE; + pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR); + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + if (pa_tagstruct_get( + t, + PA_TAG_U32, &maxlength, + PA_TAG_U32, &fragsize, + PA_TAG_INVALID) < 0 || + (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + fix_record_buffer_attr_pre(s, adjust_latency, &maxlength, &fragsize); + pa_memblockq_set_maxlength(s->memblockq, maxlength); + fix_record_buffer_attr_post(s, &maxlength, &fragsize); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, maxlength); + pa_tagstruct_putu32(reply, fragsize); + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->source_latency); + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + 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_update_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + uint32_t mode; + pa_proplist *p; + + connection_assert_ref(c); + pa_assert(t); + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + p = pa_proplist_new(); + + if (command == PA_COMMAND_UPDATE_CLIENT_PROPLIST) { + + if (pa_tagstruct_getu32(t, &mode) < 0 || + pa_tagstruct_get_proplist(t, p) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } + + } else { + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_getu32(t, &mode) < 0 || + pa_tagstruct_get_proplist(t, p) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } + } + + CHECK_VALIDITY(c->pstream, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_proplist_update(s->sink_input->proplist, mode, p); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->sink_input->index); + + } else if (command == PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST) { + record_stream *s; + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_proplist_update(s->source_output->proplist, mode, p); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->source_output->index); + } else { + pa_assert(command == PA_COMMAND_UPDATE_CLIENT_PROPLIST); + + pa_proplist_update(c->client->proplist, mode, p); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_remove_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + unsigned changed = 0; + pa_proplist *p; + pa_strlist *l = NULL; + + connection_assert_ref(c); + pa_assert(t); + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + if (command != PA_COMMAND_REMOVE_CLIENT_PROPLIST) { + + if (pa_tagstruct_getu32(t, &idx) < 0) { + protocol_error(c); + return; + } + } + + if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + p = s->sink_input->proplist; + + } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) { + record_stream *s; + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + p = s->source_output->proplist; + } else { + pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST); + + p = c->client->proplist; + } + + for (;;) { + const char *k; + + if (pa_tagstruct_gets(t, &k) < 0) { + protocol_error(c); + pa_strlist_free(l); + return; + } + + if (!k) + break; + + l = pa_strlist_prepend(l, k); + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + pa_strlist_free(l); + return; + } + + for (;;) { + char *z; + + l = pa_strlist_pop(l, &z); + + if (!z) + break; + + changed += pa_proplist_unset(p, z) >= 0; + pa_xfree(z); + } + + pa_pstream_send_simple_ack(c->pstream, tag); + + if (changed) { + if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->sink_input->index); + + } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) { + record_stream *s; + + s = pa_idxset_get_by_index(c->record_streams, idx); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->source_output->index); + + } else { + pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index); + } + } +} + static void command_set_default_sink_or_source(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); const char *s; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_gets(t, &s) < 0 || !pa_tagstruct_eof(t)) { @@ -1903,10 +3386,12 @@ static void command_set_default_sink_or_source(PA_GCC_UNUSED pa_pdispatch *pd, u } static void command_set_stream_name(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; const char *name; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || pa_tagstruct_gets(t, &name) < 0 || @@ -1914,22 +3399,23 @@ static void command_set_stream_name(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t com 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) { - struct playback_stream *s; - + 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, s->type == PLAYBACK_STREAM, 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 { - struct record_stream *s; - + 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); @@ -1940,9 +3426,11 @@ static void command_set_stream_name(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t com } static void command_kill(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || !pa_tagstruct_eof(t)) { @@ -1954,38 +3442,45 @@ static void command_kill(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint3 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; - assert(command == PA_COMMAND_KILL_SOURCE_OUTPUT); - + 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_module *m; const char *name, *argument; pa_tagstruct *reply; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_gets(t, &name) < 0 || pa_tagstruct_gets(t, &argument) < 0 || @@ -1997,7 +3492,7 @@ static void command_load_module(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED ui 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; @@ -2009,17 +3504,19 @@ static void command_load_module(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED ui } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx; pa_module *m; - assert(c && t); + + 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); @@ -2029,22 +3526,24 @@ static void command_unload_module(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); const char *name, *module, *argument; uint32_t type; uint32_t idx; pa_tagstruct *reply; - assert(c && t); - if (pa_tagstruct_gets(t, &name) < 0 || - pa_tagstruct_getu32(t, &type) < 0 || - pa_tagstruct_gets(t, &module) < 0 || + 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); @@ -2062,11 +3561,13 @@ static void command_add_autoload(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED u } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); const char *name = NULL; uint32_t type, idx = PA_IDXSET_INVALID; int r; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if ((pa_tagstruct_getu32(t, &idx) < 0 && (pa_tagstruct_gets(t, &name) < 0 || @@ -2075,12 +3576,12 @@ static void command_remove_autoload(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSE 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) + 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); @@ -2091,7 +3592,7 @@ static void command_remove_autoload(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSE } static void autoload_fill_tagstruct(pa_tagstruct *t, const pa_autoload_entry *e) { - assert(t && e); + pa_assert(t && e); pa_tagstruct_putu32(t, e->index); pa_tagstruct_puts(t, e->name); @@ -2101,12 +3602,14 @@ static void autoload_fill_tagstruct(pa_tagstruct *t, const pa_autoload_entry *e) } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); const pa_autoload_entry *a = NULL; uint32_t type, idx; const char *name; pa_tagstruct *reply; - assert(c && t); + + connection_assert_ref(c); + pa_assert(t); if ((pa_tagstruct_getu32(t, &idx) < 0 && (pa_tagstruct_gets(t, &name) < 0 || @@ -2133,15 +3636,17 @@ static void command_get_autoload_info(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNU } 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); pa_tagstruct *reply; - assert(c && t); + + 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); @@ -2153,17 +3658,17 @@ static void command_get_autoload_info_list(PA_GCC_UNUSED pa_pdispatch *pd, PA_GC 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) { - struct connection *c = userdata; + connection *c = CONNECTION(userdata); uint32_t idx = PA_INVALID_INDEX, idx_device = PA_INVALID_INDEX; const char *name = NULL; - - assert(c); - assert(t); + + connection_assert_ref(c); + pa_assert(t); if (pa_tagstruct_getu32(t, &idx) < 0 || pa_tagstruct_getu32(t, &idx_device) < 0 || @@ -2172,7 +3677,7 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag 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); @@ -2182,7 +3687,7 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag 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 @@ -2190,7 +3695,7 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag CHECK_VALIDITY(c->pstream, si && sink, tag, PA_ERR_NOENTITY); - if (pa_sink_input_move_to(si, sink, 0) < 0) { + if (pa_sink_input_move_to(si, sink) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -2198,8 +3703,10 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag 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 @@ -2212,70 +3719,124 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag 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; + pa_bool_t 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) { - struct connection *c = userdata; - assert(p && packet && packet->data && c); + 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_free(c); + 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) { - struct connection *c = userdata; - struct output_stream *stream; - assert(p && chunk && userdata); - - if (!(stream = pa_idxset_get_by_index(c->output_streams, channel))) { + 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 (stream->type == PLAYBACK_STREAM) { - struct playback_stream *ps = (struct playback_stream*) stream; - if (chunk->length >= ps->requested_bytes) - ps->requested_bytes = 0; - else - ps->requested_bytes -= chunk->length; - - pa_memblockq_seek(ps->memblockq, offset, seek); - - if (pa_memblockq_push_align(ps->memblockq, chunk) < 0) { - pa_tagstruct *t; - - pa_log_warn("failed to push data into queue"); - - /* Pushing this block into the queue failed, so we simulate - * it by skipping ahead */ - - pa_memblockq_seek(ps->memblockq, chunk->length, PA_SEEK_RELATIVE); + if (playback_stream_isinstance(stream)) { + playback_stream *ps = PLAYBACK_STREAM(stream); - /* Notify the user */ - 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, ps->index); - pa_pstream_send_tagstruct(p, t); - } + 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); - ps->underrun = 0; - - pa_sink_notify(ps->sink_input->sink); + pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); } else { - struct upload_stream *u = (struct upload_stream*) stream; + upload_stream *u = UPLOAD_STREAM(stream); size_t l; - - assert(u->type == UPLOAD_STREAM); if (!u->memchunk.memblock) { if (u->length == chunk->length) { @@ -2287,25 +3848,25 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o u->memchunk.index = u->memchunk.length = 0; } } - - assert(u->memchunk.memblock); - - l = u->length; + + 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; } @@ -2313,43 +3874,71 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o } static void pstream_die_callback(pa_pstream *p, void *userdata) { - struct connection *c = userdata; - assert(p && c); - connection_free(c); + connection *c = CONNECTION(userdata); -/* pa_log("connection died.");*/ -} + 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) { - struct connection *c = userdata; - assert(p && c); + 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) { - assert(c && c->userdata); - connection_free(c->userdata); + 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) { - struct connection *c = userdata; - assert(m && tv && c && c->auth_timeout_event == e); + 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_free(c); + connection_unlink(c); } static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, void *userdata) { pa_protocol_native *p = userdata; - struct connection *c; + connection *c; char cname[256], pname[128]; - assert(io && p); + + 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); @@ -2357,15 +3946,17 @@ static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, vo return; } - c = pa_xmalloc(sizeof(struct connection)); + c = pa_msgobject_new(connection); + c->parent.parent.free = connection_free; + c->parent.process_msg = connection_process_msg; - c->authorized = !!p->public; + 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; + c->authorized = TRUE; } - + if (!c->authorized) { struct timeval tv; pa_gettimeofday(&tv); @@ -2374,59 +3965,56 @@ static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, vo } else c->auth_timeout_event = NULL; + c->is_local = pa_iochannel_socket_is_local(io); c->version = 8; c->protocol = p; pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); - snprintf(cname, sizeof(cname), "Native client (%s)", pname); - assert(p->core); + pa_snprintf(cname, sizeof(cname), "Native client (%s)", pname); c->client = pa_client_new(p->core, __FILE__, cname); - assert(c->client); + pa_proplist_sets(c->client->proplist, "native-protocol.peer", pname); c->client->kill = client_kill_cb; c->client->userdata = c; - c->client->owner = p->module; - + c->client->module = p->module; + c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool); - assert(c->pstream); 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); - assert(c->pdispatch); c->record_streams = pa_idxset_new(NULL, NULL); c->output_streams = pa_idxset_new(NULL, NULL); - assert(c->record_streams && c->output_streams); 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) { - assert(p); + pa_assert(p); + + p->auth_cookie_in_property = FALSE; - 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; + p->auth_cookie_in_property = TRUE; return 0; } - + if (!fn) fn = PA_NATIVE_COOKIE_FILE; @@ -2436,39 +4024,39 @@ static int load_key(pa_protocol_native*p, const char*fn) { 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; - + p->auth_cookie_in_property = TRUE; + return 0; } static pa_protocol_native* protocol_new_internal(pa_core *c, pa_module *m, pa_modargs *ma) { pa_protocol_native *p; - int public = 0; + pa_bool_t public = FALSE; const char *acl; - - assert(c); - assert(ma); + + 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 { - int a = 1; + pa_bool_t a = TRUE; 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; + p->auth_group = a ? pa_xstrdup(pa_modargs_get_value(ma, "auth-group", pa_in_system_mode() ? PA_ACCESS_GROUP : NULL)) : NULL; if (p->auth_group) pa_log_info("Allowing access to group '%s'.", p->auth_group); @@ -2488,7 +4076,6 @@ static pa_protocol_native* protocol_new_internal(pa_core *c, pa_module *m, pa_mo goto fail; p->connections = pa_idxset_new(NULL, NULL); - assert(p->connections); return p; @@ -2508,8 +4095,8 @@ pa_protocol_native* pa_protocol_native_new(pa_core *core, pa_socket_server *serv if (!(p = protocol_new_internal(core, m, ma))) return NULL; - - p->server = server; + + p->server = pa_socket_server_ref(server); pa_socket_server_set_callback(p->server, on_connection, p); if (pa_socket_server_get_address(p->server, t, sizeof(t))) { @@ -2518,21 +4105,21 @@ pa_protocol_native* pa_protocol_native_new(pa_core *core, pa_socket_server *serv 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) { - struct connection *c; - assert(p); + connection *c; + pa_assert(p); while ((c = pa_idxset_first(p->connections, NULL))) - connection_free(c); + 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); @@ -2543,7 +4130,7 @@ void pa_protocol_native_free(pa_protocol_native *p) { else pa_property_remove(p->core, PA_NATIVE_SERVER_PROPERTY_NAME); } - + pa_socket_server_unref(p->server); } @@ -2552,20 +4139,25 @@ void pa_protocol_native_free(pa_protocol_native *p) { 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* 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 index 5b091014..a52fa8cf 100644 --- a/src/pulsecore/protocol-native.h +++ b/src/pulsecore/protocol-native.h @@ -1,21 +1,22 @@ #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 diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index bf203e42..020a281d 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,7 +23,6 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <limits.h> #include <stdio.h> @@ -31,6 +30,7 @@ #include <string.h> #include <pulse/xmalloc.h> +#include <pulse/timeval.h> #include <pulsecore/sink-input.h> #include <pulsecore/source-output.h> @@ -39,281 +39,438 @@ #include <pulsecore/namereg.h> #include <pulsecore/log.h> #include <pulsecore/core-error.h> +#include <pulsecore/atomic.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/core-util.h> #include "protocol-simple.h" /* Don't allow more than this many concurrent connections */ #define MAX_CONNECTIONS 10 -struct connection { +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; - pa_defer_event *defer_event; - int dead; - + pa_bool_t dead; + struct { pa_memblock *current_memblock; - size_t memblock_index, fragment_size; + size_t memblock_index; + pa_atomic_t missing; + pa_bool_t underrun; } 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) +#define DEFAULT_SINK_LATENCY (300*PA_USEC_PER_MSEC) +#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC) -static void connection_free(struct connection *c) { - assert(c); +static void connection_unlink(connection *c) { + pa_assert(c); - pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + if (!c->protocol) + return; - if (c->playback.current_memblock) - pa_memblock_unref(c->playback.current_memblock); if (c->sink_input) { - pa_sink_input_disconnect(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_disconnect(c->source_output); + pa_source_output_unlink(c->source_output); pa_source_output_unref(c->source_output); + c->source_output = NULL; } - if (c->client) + + if (c->client) { pa_client_free(c->client); - if (c->io) + 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); + + 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); - if (c->defer_event) - c->protocol->core->mainloop->defer_free(c->defer_event); + pa_xfree(c); } -static int do_read(struct connection *c) { +static int do_read(connection *c) { pa_memchunk chunk; ssize_t r; size_t l; void *p; + size_t space; + + connection_assert_ref(c); - if (!c->sink_input || !(l = pa_memblockq_missing(c->input_memblockq))) + 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 (c->playback.current_memblock) - if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) { + space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index; + + if (space <= 0) { pa_memblock_unref(c->playback.current_memblock); c->playback.current_memblock = NULL; - c->playback.memblock_index = 0; } + } if (!c->playback.current_memblock) { - c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, c->playback.fragment_size*2); - assert(c->playback.current_memblock); - assert(pa_memblock_get_length(c->playback.current_memblock) >= l); + pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, 0)); c->playback.memblock_index = 0; + + space = pa_memblock_get_length(c->playback.current_memblock); } + if (l > space) + l = space; + p = pa_memblock_acquire(c->playback.current_memblock); - - if ((r = pa_iochannel_read(c->io, (uint8_t*) p + c->playback.memblock_index, l)) <= 0) { - pa_memblock_release(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; } - pa_memblock_release(c->playback.current_memblock); chunk.memblock = c->playback.current_memblock; chunk.index = c->playback.memblock_index; chunk.length = r; - assert(chunk.memblock); c->playback.memblock_index += r; - - assert(c->input_memblockq); - pa_memblockq_push_align(c->input_memblockq, &chunk); - assert(c->sink_input); - pa_sink_notify(c->sink_input->sink); - + + 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(struct connection *c) { +static int do_write(connection *c) { pa_memchunk chunk; ssize_t r; void *p; - + + connection_assert_ref(c); + if (!c->source_output) - return 0; + return 0; - assert(c->output_memblockq); - if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) + if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) { +/* pa_log("peek failed"); */ return 0; - - assert(chunk.memblock && chunk.length); + } + + pa_assert(chunk.memblock); + pa_assert(chunk.length); p = pa_memblock_acquire(chunk.memblock); - - if ((r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length)) < 0) { - pa_memblock_release(chunk.memblock); - pa_memblock_unref(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_memblock_release(chunk.memblock); - - pa_memblockq_drop(c->output_memblockq, &chunk, r); - pa_memblock_unref(chunk.memblock); + pa_memblockq_drop(c->output_memblockq, r); - pa_source_notify(c->source_output->source); - return 0; } -static void do_work(struct connection *c) { - assert(c); - - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable); - c->protocol->core->mainloop->defer_enable(c->defer_event, 0); +static void do_work(connection *c) { + connection_assert_ref(c); if (c->dead) return; - - if (pa_iochannel_is_readable(c->io)) { + + if (pa_iochannel_is_readable(c->io)) if (do_read(c) < 0) goto fail; - } else if (pa_iochannel_is_hungup(c->io)) + + if (!c->sink_input && pa_iochannel_is_hungup(c->io)) goto fail; - if (pa_iochannel_is_writable(c->io)) { + if (pa_iochannel_is_writable(c->io)) if (do_write(c) < 0) goto fail; - } return; fail: if (c->sink_input) { - c->dead = 1; - + + /* If there is a sink input, we first drain what we already have read before shutting down the connection */ + c->dead = TRUE; + pa_iochannel_free(c->io); c->io = NULL; - pa_memblockq_prebuf_disable(c->input_memblockq); - pa_sink_notify(c->sink_input->sink); + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL); } else - connection_free(c); + 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 ***/ -static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) { - struct connection*c; - assert(i && i->userdata && chunk); - c = i->userdata; - +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + switch (code) { + + case SINK_INPUT_MESSAGE_POST_DATA: { + pa_assert(chunk); + + /* New data from the main loop */ + pa_memblockq_push_align(c->input_memblockq, chunk); + + if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) { + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(c->sink_input, 0, FALSE, TRUE); + } + +/* 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_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(chunk); + if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) { - - if (c->dead) - connection_free(c); - + + c->playback.underrun = TRUE; + + if (c->dead && pa_sink_input_safe_to_remove(i)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); + return -1; - } + } else { + size_t m; - return 0; + chunk->length = PA_MIN(length, chunk->length); + + c->playback.underrun = FALSE; + + pa_memblockq_drop(c->input_memblockq, chunk->length); + m = pa_memblockq_pop_missing(c->input_memblockq); + + if (m > 0) + if (pa_atomic_add(&c->playback.missing, m) <= 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); + + return 0; + } } -static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { - struct connection*c = i->userdata; - assert(i && c && length); +/* Called from thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); - pa_memblockq_drop(c->input_memblockq, chunk, length); + /* If we are in an underrun, then we don't rewind */ + if (i->thread_info.underrun_for > 0) + return; - /* do something */ - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable); - c->protocol->core->mainloop->defer_enable(c->defer_event, 1); + pa_memblockq_rewind(c->input_memblockq, nbytes); } -static void sink_input_kill_cb(pa_sink_input *i) { - assert(i && i->userdata); - connection_free((struct connection *) i->userdata); +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + pa_memblockq_set_maxrewind(c->input_memblockq, nbytes); } +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); -static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) { - struct connection*c = i->userdata; - assert(i && c); - return pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); + 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) { - struct connection *c = o->userdata; - assert(o && c && chunk); + connection *c; - pa_memblockq_push(c->output_memblockq, chunk); + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + pa_assert(chunk); - /* do something */ - assert(c->protocol && c->protocol->core && c->protocol->core->mainloop && c->protocol->core->mainloop->defer_enable); - c->protocol->core->mainloop->defer_enable(c->defer_event, 1); + 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) { - assert(o && o->userdata); - connection_free((struct connection *) o->userdata); + 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) { - struct connection*c = o->userdata; - assert(o && c); + connection*c; + + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); } /*** client callbacks ***/ -static void client_kill_cb(pa_client *c) { - assert(c && c->userdata); - connection_free((struct connection *) c->userdata); +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) { - struct connection *c = userdata; - assert(io && c && c->io == io); - - do_work(c); -} - -/*** fixed callback ***/ + connection *c = CONNECTION(userdata); -static void defer_callback(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { - struct connection *c = userdata; - assert(a && c && c->defer_event == e); + connection_assert_ref(c); + pa_assert(io); do_work(c); } @@ -322,9 +479,12 @@ static void defer_callback(pa_mainloop_api*a, pa_defer_event *e, void *userdata) static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { pa_protocol_simple *p = userdata; - struct connection *c = NULL; - char cname[256]; - assert(s && io && p); + connection *c = NULL; + 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); @@ -332,75 +492,102 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) return; } - c = pa_xmalloc(sizeof(struct connection)); + 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->defer_event = 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_iochannel_socket_peer_to_string(io, cname, sizeof(cname)); - c->client = pa_client_new(p->core, __FILE__, cname); - assert(c->client); - c->client->owner = p->module; + c->dead = FALSE; + c->playback.underrun = TRUE; + pa_atomic_store(&c->playback.missing, 0); + + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_snprintf(cname, sizeof(cname), "Simple client (%s)", pname); + pa_assert_se(c->client = pa_client_new(p->core, __FILE__, cname)); + pa_proplist_sets(c->client->proplist, "simple-protocol.peer", pname); + c->client->module = 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 *sink; + + if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, TRUE))) { + pa_log("Failed to get sink."); + goto fail; + } 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; + data.sink = sink; + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_sink_input_new_data_set_sample_spec(&data, &p->sample_spec); + + c->sink_input = pa_sink_input_new(p->core, &data, 0); + pa_sink_input_new_data_done(&data); - if (!(c->sink_input = pa_sink_input_new(p->core, &data, 0))) { + if (!c->sink_input) { pa_log("Failed to create sink input."); goto fail; } - - c->sink_input->peek = sink_input_peek_cb; - c->sink_input->drop = sink_input_drop_cb; + + c->sink_input->parent.process_msg = sink_input_process_msg; + c->sink_input->pop = sink_input_pop_cb; + c->sink_input->process_rewind = sink_input_process_rewind_cb; + c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; c->sink_input->kill = sink_input_kill_cb; - c->sink_input->get_latency = sink_input_get_latency_cb; c->sink_input->userdata = c; + pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY); + l = (size_t) (pa_bytes_per_second(&p->sample_spec)*PLAYBACK_BUFFER_SECONDS); c->input_memblockq = pa_memblockq_new( 0, l, - 0, + l, pa_frame_size(&p->sample_spec), (size_t) -1, l/PLAYBACK_BUFFER_FRAGMENTS, + 0, NULL); - assert(c->input_memblockq); - pa_iochannel_socket_set_rcvbuf(io, l/PLAYBACK_BUFFER_FRAGMENTS*5); - c->playback.fragment_size = l/10; + pa_iochannel_socket_set_rcvbuf(io, l); + + pa_atomic_store(&c->playback.missing, pa_memblockq_missing(c->input_memblockq)); - pa_sink_notify(c->sink_input->sink); + pa_sink_input_put(c->sink_input); } if (p->mode & RECORD) { pa_source_output_new_data data; size_t l; + pa_source *source; + + if (!(source = pa_namereg_get(c->protocol->core, c->protocol->source_name, PA_NAMEREG_SOURCE, TRUE))) { + pa_log("Failed to get source."); + goto fail; + } 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; + data.source = source; + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_source_output_new_data_set_sample_spec(&data, &p->sample_spec); - if (!(c->source_output = pa_source_output_new(p->core, &data, 0))) { + c->source_output = pa_source_output_new(p->core, &data, 0); + pa_source_output_new_data_done(&data); + + if (!c->source_output) { pa_log("Failed to create source output."); goto fail; } @@ -409,6 +596,8 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) c->source_output->get_latency = source_output_get_latency_cb; c->source_output->userdata = c; + pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + l = (size_t) (pa_bytes_per_second(&p->sample_spec)*RECORD_BUFFER_SECONDS); c->output_memblockq = pa_memblockq_new( 0, @@ -417,34 +606,36 @@ static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) pa_frame_size(&p->sample_spec), 1, 0, + 0, NULL); - pa_iochannel_socket_set_sndbuf(io, l/RECORD_BUFFER_FRAGMENTS*2); - pa_source_notify(c->source_output->source); + pa_iochannel_socket_set_sndbuf(io, l); + + pa_source_output_put(c->source_output); } pa_iochannel_set_callback(c->io, io_callback, c); pa_idxset_put(p->connections, c, NULL); - c->defer_event = p->core->mainloop->defer_new(p->core->mainloop, defer_callback, c); - assert(c->defer_event); - p->core->mainloop->defer_enable(c->defer_event, 0); - return; - + fail: if (c) - connection_free(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; - int enable; - assert(core && server && ma); + pa_bool_t enable; + + pa_assert(core); + pa_assert(server); + pa_assert(m); + pa_assert(ma); - p = pa_xmalloc0(sizeof(pa_protocol_simple)); + p = pa_xnew0(pa_protocol_simple, 1); p->module = m; p->core = core; - p->server = server; + p->server = pa_socket_server_ref(server); p->connections = pa_idxset_new(NULL, NULL); p->sample_spec = core->default_sample_spec; @@ -455,15 +646,15 @@ pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *serv 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 = 0; + + 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; + enable = TRUE; if (pa_modargs_get_value_boolean(ma, "playback", &enable) < 0) { pa_log("playback= expects a numeric argument."); goto fail; @@ -474,31 +665,32 @@ pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *serv 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) { - struct connection *c; - assert(p); + connection *c; + pa_assert(p); if (p->connections) { while((c = pa_idxset_first(p->connections, NULL))) - connection_free(c); - + 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 index 8dfaee34..e1b3143b 100644 --- a/src/pulsecore/protocol-simple.h +++ b/src/pulsecore/protocol-simple.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/pstream-util.c b/src/pulsecore/pstream-util.c index d7c1b31b..f84f486a 100644 --- a/src/pulsecore/pstream-util.c +++ b/src/pulsecore/pstream-util.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -23,9 +23,8 @@ #include <config.h> #endif -#include <assert.h> - #include <pulsecore/native-common.h> +#include <pulsecore/macro.h> #include "pstream-util.h" @@ -33,20 +32,20 @@ void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const size_t length; uint8_t *data; pa_packet *packet; - assert(p); - assert(t); - data = pa_tagstruct_free_data(t, &length); - assert(data && length); - packet = pa_packet_new_dynamic(data, length); - assert(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_tagstruct_new(NULL, 0); - assert(t); + 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); @@ -54,8 +53,9 @@ 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) { - pa_tagstruct *t = pa_tagstruct_new(NULL, 0); - assert(t); + 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 index c6d76a7c..ae0d79c3 100644 --- a/src/pulsecore/pstream-util.h +++ b/src/pulsecore/pstream-util.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c index 33963796..e26ca473 100644 --- a/src/pulsecore/pstream.c +++ b/src/pulsecore/pstream.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,7 +26,6 @@ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <unistd.h> #ifdef HAVE_SYS_SOCKET_H @@ -38,17 +38,17 @@ #include <netinet/in.h> #endif -#include "winsock.h" #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/mutex.h> #include <pulsecore/refcnt.h> -#include <pulsecore/anotify.h> +#include <pulsecore/flist.h> +#include <pulsecore/macro.h> #include "pstream.h" @@ -82,7 +82,8 @@ 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 */ -#define FRAME_SIZE_MAX_USE (1024*64) + +PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); struct item_info { enum { @@ -92,11 +93,10 @@ struct item_info { PA_PSTREAM_ITEM_SHMREVOKE } type; - /* packet info */ pa_packet *packet; #ifdef HAVE_CREDS - int with_creds; + pa_bool_t with_creds; pa_creds creds; #endif @@ -112,15 +112,14 @@ struct item_info { struct pa_pstream { PA_REFCNT_DECLARE; - + pa_mainloop_api *mainloop; + pa_defer_event *defer_event; pa_iochannel *io; pa_queue *send_queue; - pa_mutex *mutex; /* only for access to the queue */ - pa_anotify *anotify; - int dead; + pa_bool_t dead; struct { pa_pstream_descriptor descriptor; @@ -140,7 +139,7 @@ struct pa_pstream { size_t index; } read; - int use_shm; + pa_bool_t use_shm; pa_memimport *import; pa_memexport *export; @@ -156,11 +155,17 @@ struct pa_pstream { 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; + pa_bool_t read_creds_valid, send_creds_now; #endif }; @@ -168,11 +173,13 @@ static int do_write(pa_pstream *p); static int do_read(pa_pstream *p); static void do_something(pa_pstream *p) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + 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; @@ -189,27 +196,31 @@ static void do_something(pa_pstream *p) { fail: - p->dead = 1; - 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; - - assert(p); - assert(p->io == io); - + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->io == io); + do_something(p); } -static void anotify_callback(uint8_t event, void *userdata) { +static void defer_callback(pa_mainloop_api *m, pa_defer_event *e, void*userdata) { pa_pstream *p = userdata; - assert(p); + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->defer_event == e); + pa_assert(p->mainloop == m); + do_something(p); } @@ -217,24 +228,22 @@ static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userd pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *pool) { pa_pstream *p; - - assert(m); - assert(io); - assert(pool); + + 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->mutex = pa_mutex_new(1); - p->anotify = pa_anotify_new(m, anotify_callback, p); + p->dead = FALSE; 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(); - assert(p->send_queue); p->write.current = NULL; p->write.index = 0; @@ -251,64 +260,63 @@ pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *poo 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->use_shm = FALSE; 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); + pa_iochannel_socket_set_rcvbuf(io, pa_mempool_block_size_max(p->mempool)); + pa_iochannel_socket_set_sndbuf(io, pa_mempool_block_size_max(p->mempool)); #ifdef HAVE_CREDS - p->send_creds_now = 0; - p->read_creds_valid = 0; + p->send_creds_now = FALSE; + p->read_creds_valid = FALSE; #endif return p; } -static void item_free(void *item, PA_GCC_UNUSED void *p) { +static void item_free(void *item, PA_GCC_UNUSED void *q) { struct item_info *i = item; - assert(i); + pa_assert(i); if (i->type == PA_PSTREAM_ITEM_MEMBLOCK) { - assert(i->chunk.memblock); + pa_assert(i->chunk.memblock); pa_memblock_unref(i->chunk.memblock); } else if (i->type == PA_PSTREAM_ITEM_PACKET) { - assert(i->packet); + pa_assert(i->packet); pa_packet_unref(i->packet); } - pa_xfree(i); + if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0) + pa_xfree(i); } static void pstream_free(pa_pstream *p) { - assert(p); + pa_assert(p); + + pa_pstream_unlink(p); - pa_pstream_close(p); - pa_queue_free(p->send_queue, item_free, NULL); if (p->write.current) item_free(p->write.current, NULL); - if (p->read.memblock) - pa_memblock_unref(p->read.memblock); - - if (p->read.packet) - pa_packet_unref(p->read.packet); - if (p->write.memchunk.memblock) pa_memblock_unref(p->write.memchunk.memblock); - if (p->mutex) - pa_mutex_free(p->mutex); + if (p->read.memblock) + pa_memblock_unref(p->read.memblock); - if (p->anotify) - pa_anotify_free(p->anotify); + if (p->read.packet) + pa_packet_unref(p->read.packet); pa_xfree(p); } @@ -316,123 +324,156 @@ static void pstream_free(pa_pstream *p) { void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_creds *creds) { struct item_info *i; - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); - assert(packet); + 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 = 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_mutex_lock(p->mutex); pa_queue_push(p->send_queue, i); - pa_mutex_unlock(p->mutex); - - pa_anotify_signal(p->anotify, 0); + + 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; - - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); - assert(channel != (uint32_t) -1); - assert(chunk); + 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; - - i = pa_xnew(struct item_info, 1); + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(struct item_info, 1); i->type = PA_PSTREAM_ITEM_MEMBLOCK; - n = length < FRAME_SIZE_MAX_USE ? length : FRAME_SIZE_MAX_USE; + n = PA_MIN(length, bsm); i->chunk.index = chunk->index + idx; i->chunk.length = n; i->chunk.memblock = pa_memblock_ref(chunk->memblock); - + i->channel = channel; i->offset = offset; i->seek_mode = seek_mode; #ifdef HAVE_CREDS - i->with_creds = 0; + i->with_creds = FALSE; #endif - - pa_mutex_lock(p->mutex); + pa_queue_push(p->send_queue, i); - pa_mutex_unlock(p->mutex); idx += n; length -= n; } - pa_anotify_signal(p->anotify, 0); + p->mainloop->defer_enable(p->defer_event, 1); } -static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata) { +void pa_pstream_send_release(pa_pstream *p, uint32_t block_id) { struct item_info *item; - pa_pstream *p = userdata; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + if (p->dead) + return; /* pa_log("Releasing block %u", block_id); */ - item = pa_xnew(struct item_info, 1); + 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; + item->with_creds = FALSE; #endif - pa_mutex_lock(p->mutex); pa_queue_push(p->send_queue, item); - pa_mutex_unlock(p->mutex); - - pa_anotify_signal(p->anotify, 0); + p->mainloop->defer_enable(p->defer_event, 1); } -static void memexport_revoke_cb(pa_memexport *e, uint32_t block_id, void *userdata) { - struct item_info *item; +/* might be called from thread context */ +static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata) { pa_pstream *p = userdata; - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + 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); */ - - item = pa_xnew(struct item_info, 1); + + 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; + item->with_creds = FALSE; #endif - pa_mutex_lock(p->mutex); pa_queue_push(p->send_queue, item); - pa_mutex_unlock(p->mutex); + 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); - pa_anotify_signal(p->anotify, 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) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); - pa_mutex_lock(p->mutex); p->write.current = pa_queue_pop(p->send_queue); - pa_mutex_unlock(p->mutex); if (!p->write.current) return; - + p->write.index = 0; p->write.data = NULL; pa_memchunk_reset(&p->write.memchunk); @@ -442,10 +483,10 @@ static void prepare_next_write_item(pa_pstream *p) { 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) { - - assert(p->write.current->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); @@ -458,14 +499,14 @@ static void prepare_next_write_item(pa_pstream *p) { 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; - - assert(p->write.current->type == PA_PSTREAM_ITEM_MEMBLOCK); - assert(p->write.current->chunk.memblock); - + pa_bool_t send_payload = TRUE; + + pa_assert(p->write.current->type == PA_PSTREAM_ITEM_MEMBLOCK); + pa_assert(p->write.current->chunk.memblock); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl(p->write.current->channel); p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl((uint32_t) (((uint64_t) p->write.current->offset) >> 32)); p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = htonl((uint32_t) ((uint64_t) p->write.current->offset)); @@ -476,7 +517,7 @@ static void prepare_next_write_item(pa_pstream *p) { uint32_t block_id, shm_id; size_t offset, length; - assert(p->export); + pa_assert(p->export); if (pa_memexport_put(p->export, p->write.current->chunk.memblock, @@ -484,15 +525,15 @@ static void prepare_next_write_item(pa_pstream *p) { &shm_id, &offset, &length) >= 0) { - + flags |= PA_FLAG_SHMDATA; - send_payload = 0; - + send_payload = FALSE; + 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; } @@ -506,7 +547,7 @@ static void prepare_next_write_item(pa_pstream *p) { pa_memblock_ref(p->write.memchunk.memblock); p->write.data = NULL; } - + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(flags); } @@ -521,9 +562,9 @@ static int do_write(pa_pstream *p) { size_t l; ssize_t r; pa_memblock *release_memblock = NULL; - - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); if (!p->write.current) prepare_next_write_item(p); @@ -535,7 +576,7 @@ static int do_write(pa_pstream *p) { d = (uint8_t*) p->write.descriptor + p->write.index; l = PA_PSTREAM_DESCRIPTOR_SIZE - p->write.index; } else { - assert(p->write.data || p->write.memchunk.memblock); + pa_assert(p->write.data || p->write.memchunk.memblock); if (p->write.data) d = p->write.data; @@ -543,20 +584,20 @@ static int do_write(pa_pstream *p) { 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); } - assert(l > 0); - + 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; + p->send_creds_now = FALSE; } else #endif @@ -569,10 +610,15 @@ static int do_write(pa_pstream *p) { p->write.index += r; if (p->write.index >= PA_PSTREAM_DESCRIPTOR_SIZE + ntohl(p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH])) { - assert(p->write.current); - item_free(p->write.current, (void *) 1); + 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); } @@ -583,24 +629,23 @@ fail: if (release_memblock) pa_memblock_release(release_memblock); - + return -1; } static int do_read(pa_pstream *p) { void *d; - size_t l; + size_t l; ssize_t r; pa_memblock *release_memblock = NULL; - - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); - + 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 { - assert(p->read.data || p->read.memblock); + pa_assert(p->read.data || p->read.memblock); if (p->read.data) d = p->read.data; @@ -608,15 +653,15 @@ static int do_read(pa_pstream *p) { 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 { - int b = 0; - + pa_bool_t b = 0; + if ((r = pa_iochannel_read_with_creds(p->io, d, l, &p->read_creds, &b)) <= 0) goto fail; @@ -629,7 +674,7 @@ static int do_read(pa_pstream *p) { if (release_memblock) pa_memblock_release(release_memblock); - + p->read.index += r; if (p->read.index == PA_PSTREAM_DESCRIPTOR_SIZE) { @@ -638,97 +683,97 @@ static int do_read(pa_pstream *p) { flags = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]); - if (!p->import && (flags & PA_FLAG_SHMMASK) != 0) { + 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])); */ - - assert(p->export); + + 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])); */ - assert(p->import); + 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) { - pa_log_warn("Recieved invalid frame size : %lu", (unsigned long) length); + + if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) { + pa_log_warn("Recieved invalid frame size: %lu", (unsigned long) length); return -1; } - - assert(!p->read.packet && !p->read.memblock); + + 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; @@ -739,7 +784,7 @@ static int do_read(pa_pstream *p) { 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]), @@ -758,14 +803,14 @@ static int do_read(pa_pstream *p) { /* 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); @@ -776,10 +821,10 @@ static int do_read(pa_pstream *p) { pa_packet_unref(p->read.packet); } else { pa_memblock *b; - - assert((ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SHMMASK) == PA_FLAG_SHMDATA); - assert(p->import); + 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]), @@ -794,7 +839,7 @@ static int do_read(pa_pstream *p) { if (p->recieve_memblock_callback) { int64_t offset; pa_memchunk chunk; - + chunk.memblock = b; chunk.index = 0; chunk.length = pa_memblock_get_length(b); @@ -802,7 +847,7 @@ static int do_read(pa_pstream *p) { 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]), @@ -828,7 +873,7 @@ frame_done: p->read.data = NULL; #ifdef HAVE_CREDS - p->read_creds_valid = 0; + p->read_creds_valid = FALSE; #endif return 0; @@ -841,75 +886,90 @@ fail: } void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); - + 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) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + 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) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + 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) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); p->recieve_memblock_callback = cb; p->recieve_memblock_callback_userdata = userdata; } -int pa_pstream_is_pending(pa_pstream *p) { - int b; +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); - assert(p); - 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; +} - pa_mutex_lock(p->mutex); +pa_bool_t pa_pstream_is_pending(pa_pstream *p) { + pa_bool_t b; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); if (p->dead) - b = 0; + b = FALSE; else b = p->write.current || !pa_queue_is_empty(p->send_queue); - pa_mutex_unlock(p->mutex); - return b; } void pa_pstream_unref(pa_pstream*p) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); + 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) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); - + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + PA_REFCNT_INC(p); return p; } -void pa_pstream_close(pa_pstream *p) { - assert(p); +void pa_pstream_unlink(pa_pstream *p) { + pa_assert(p); + + if (p->dead) + return; - p->dead = 1; + p->dead = TRUE; if (p->import) { pa_memimport_free(p->import); @@ -926,20 +986,25 @@ void pa_pstream_close(pa_pstream *p) { 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) { - assert(p); - assert(PA_REFCNT_VALUE(p) > 0); +void pa_pstream_enable_shm(pa_pstream *p, pa_bool_t 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); @@ -951,3 +1016,10 @@ void pa_pstream_use_shm(pa_pstream *p, int enable) { } } } + +pa_bool_t pa_pstream_get_shm(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + return p->use_shm; +} diff --git a/src/pulsecore/pstream.h b/src/pulsecore/pstream.h index 26bb7699..a528b25c 100644 --- a/src/pulsecore/pstream.h +++ b/src/pulsecore/pstream.h @@ -1,21 +1,22 @@ #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 @@ -32,30 +33,37 @@ #include <pulsecore/iochannel.h> #include <pulsecore/memchunk.h> #include <pulsecore/creds.h> +#include <pulsecore/macro.h> typedef struct pa_pstream pa_pstream; typedef void (*pa_pstream_packet_cb_t)(pa_pstream *p, pa_packet *packet, 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_unref(pa_pstream*p); + +void pa_pstream_unlink(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); +pa_bool_t pa_pstream_is_pending(pa_pstream *p); -void pa_pstream_close(pa_pstream *p); +void pa_pstream_enable_shm(pa_pstream *p, pa_bool_t enable); +pa_bool_t pa_pstream_get_shm(pa_pstream *p); #endif diff --git a/src/pulsecore/queue.c b/src/pulsecore/queue.c index 93b119eb..08fd1426 100644 --- a/src/pulsecore/queue.c +++ b/src/pulsecore/queue.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -23,13 +23,16 @@ #include <config.h> #endif -#include <assert.h> #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; @@ -42,25 +45,24 @@ struct pa_queue { 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) { - struct queue_entry *e; - assert(q); - - e = q->front; - while (e) { - struct queue_entry *n = e->next; + void *data; + pa_assert(q); + while ((data = pa_queue_pop(q))) if (destroy) - destroy(e->data, userdata); + destroy(data, userdata); - pa_xfree(e); - e = n; - } + pa_assert(!q->front); + pa_assert(!q->back); + pa_assert(q->length == 0); pa_xfree(q); } @@ -68,14 +70,20 @@ void pa_queue_free(pa_queue* q, void (*destroy)(void *p, void *userdata), void * void pa_queue_push(pa_queue *q, void *p) { struct queue_entry *e; - e = pa_xnew(struct queue_entry, 1); + 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) + if (q->back) { + pa_assert(q->front); q->back->next = e; - else { - assert(!q->front); + } else { + pa_assert(!q->front); q->front = e; } @@ -86,24 +94,30 @@ void pa_queue_push(pa_queue *q, void *p) { void* pa_queue_pop(pa_queue *q) { void *p; struct queue_entry *e; - assert(q); + pa_assert(q); if (!(e = q->front)) return NULL; q->front = e->next; - if (q->back == e) + + if (q->back == e) { + pa_assert(!e->next); q->back = NULL; + } p = e->data; - pa_xfree(e); + + 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) { - assert(q); + pa_assert(q); + return q->length == 0; } diff --git a/src/pulsecore/queue.h b/src/pulsecore/queue.h index 3fb9dea4..b09216cf 100644 --- a/src/pulsecore/queue.h +++ b/src/pulsecore/queue.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/random.c b/src/pulsecore/random.c index 7908e87d..5deac37b 100644 --- a/src/pulsecore/random.c +++ b/src/pulsecore/random.c @@ -1,18 +1,19 @@ -/* $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 @@ -28,21 +29,22 @@ #include <errno.h> #include <string.h> #include <stdlib.h> -#include <assert.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 *devices[] = { "/dev/urandom", "/dev/random", NULL }; +static const char * const devices[] = { "/dev/urandom", "/dev/random", NULL }; static int random_proper(void *ret_data, size_t length) { #ifdef OS_IS_WIN32 - assert(ret_data && length); + pa_assert(ret_data); + pa_assert(length > 0); return -1; @@ -50,21 +52,26 @@ static int random_proper(void *ret_data, size_t length) { int fd, ret = -1; ssize_t r = 0; - const char **device; + const char *const * device; - assert(ret_data && length); + pa_assert(ret_data); + pa_assert(length > 0); device = devices; while (*device) { ret = 0; - if ((fd = open(*device, O_RDONLY)) >= 0) { + if ((fd = open(*device, O_RDONLY +#ifdef O_NOCTTY + | O_NOCTTY +#endif + )) >= 0) { if ((r = pa_loop_read(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) ret = -1; - close(fd); + pa_close(fd); } else ret = -1; @@ -81,7 +88,7 @@ void pa_random_seed(void) { 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."); + pa_log_warn("Failed to get proper entropy. Falling back to seeding with current time."); has_whined = 1; seed = (unsigned int) time(NULL); @@ -94,13 +101,14 @@ void pa_random(void *ret_data, size_t length) { uint8_t *p; size_t l; - assert(ret_data && length); + 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."); + 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--) diff --git a/src/pulsecore/random.h b/src/pulsecore/random.h index b2bb3934..36d7f9df 100644 --- a/src/pulsecore/random.h +++ b/src/pulsecore/random.h @@ -1,21 +1,22 @@ #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 @@ -26,5 +27,5 @@ 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 index 6eb5ee3f..291f4504 100644 --- a/src/pulsecore/refcnt.h +++ b/src/pulsecore/refcnt.h @@ -1,21 +1,21 @@ #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 @@ -25,18 +25,21 @@ #include <pulsecore/atomic.h> #define PA_REFCNT_DECLARE \ - pa_atomic_int_t _ref + pa_atomic_t _ref #define PA_REFCNT_INIT(p) \ - pa_atomic_store(&p->_ref, 1) + pa_atomic_store(&(p)->_ref, 1) + +#define PA_REFCNT_INIT_ZERO(p) \ + pa_atomic_store(&(p)->_ref, 0) #define PA_REFCNT_INC(p) \ - pa_atomic_inc(&p->_ref) + pa_atomic_inc(&(p)->_ref) #define PA_REFCNT_DEC(p) \ - (pa_atomic_dec(&p->_ref)-1) + (pa_atomic_dec(&(p)->_ref)-1) #define PA_REFCNT_VALUE(p) \ - pa_atomic_load(&p->_ref) + pa_atomic_load(&(p)->_ref) #endif diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c index c28c2fb3..00dc794c 100644 --- a/src/pulsecore/resampler.c +++ b/src/pulsecore/resampler.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,52 +23,148 @@ #include <config.h> #endif -#include <assert.h> #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_FRAMES 128 + struct pa_resampler { - pa_resample_method_t resample_method; + 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; + size_t i_fz, o_fz, w_sz; pa_mempool *mempool; - void (*impl_free)(pa_resampler *r); - void (*impl_update_input_rate)(pa_resampler *r, uint32_t rate); - void (*impl_run)(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out); - void *impl_data; -}; - -struct impl_libsamplerate { pa_memchunk buf1, buf2, buf3, buf4; unsigned buf1_samples, buf2_samples, buf3_samples, buf4_samples; - - pa_convert_to_float32ne_func_t to_float32ne_func; - pa_convert_from_float32ne_func_t from_float32ne_func; - SRC_STATE *src_state; - int map_table[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; - int map_required; -}; + 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; + + struct { /* data specific to the peak finder pseudo resampler */ + unsigned o_counter; + unsigned i_counter; + + float max_f[PA_CHANNELS_MAX]; + int16_t max_i[PA_CHANNELS_MAX]; + + } peaks; + +#ifdef HAVE_LIBSAMPLERATE + struct { /* data specific to libsamplerate */ + SRC_STATE *state; + } src; +#endif -struct impl_trivial { - unsigned o_counter; - unsigned i_counter; + 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 libsamplerate_init(pa_resampler*r); +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); +static int peaks_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, + [PA_RESAMPLER_PEAKS] = peaks_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, @@ -76,25 +172,53 @@ pa_resampler* pa_resampler_new( const pa_channel_map *am, const pa_sample_spec *b, const pa_channel_map *bm, - pa_resample_method_t resample_method) { - + pa_resample_method_t method, + pa_resample_flags_t flags) { + pa_resampler *r = NULL; - assert(pool); - assert(a); - assert(b); - assert(pa_sample_spec_valid(a)); - assert(pa_sample_spec_valid(b)); - assert(resample_method != PA_RESAMPLER_INVALID); + 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->impl_data = NULL; r->mempool = pool; - r->resample_method = resample_method; + r->method = method; + r->flags = flags; r->impl_free = NULL; - r->impl_update_input_rate = NULL; - r->impl_run = NULL; + r->impl_update_rates = NULL; + r->impl_resample = NULL; + r->impl_reset = NULL; /* Fill sample specs */ r->i_ss = *a; @@ -102,82 +226,177 @@ pa_resampler* pa_resampler_new( if (am) r->i_cm = *am; - else - pa_channel_map_init_auto(&r->i_cm, r->i_ss.channels, PA_CHANNEL_MAP_DEFAULT); + 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 - pa_channel_map_init_auto(&r->o_cm, r->o_ss.channels, PA_CHANNEL_MAP_DEFAULT); - + 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); - /* Choose implementation */ - if (a->channels != b->channels || - a->format != b->format || - !pa_channel_map_equal(&r->i_cm, &r->o_cm) || - resample_method != PA_RESAMPLER_TRIVIAL) { + pa_memchunk_reset(&r->buf1); + pa_memchunk_reset(&r->buf2); + pa_memchunk_reset(&r->buf3); + pa_memchunk_reset(&r->buf4); - /* Use the libsamplerate based resampler for the complicated cases */ - if (resample_method == PA_RESAMPLER_TRIVIAL) - r->resample_method = PA_RESAMPLER_SRC_ZERO_ORDER_HOLD; + r->buf1_samples = r->buf2_samples = r->buf3_samples = r->buf4_samples = 0; - if (libsamplerate_init(r) < 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 || method == PA_RESAMPLER_PEAKS) { + + if (r->map_required || a->format != b->format || method == PA_RESAMPLER_PEAKS) { + + 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 { - /* Use our own simple non-fp resampler for the trivial cases and when the user selects it */ - if (trivial_init(r) < 0) + 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) { - assert(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) { - assert(r); - assert(rate > 0); + pa_assert(r); + pa_assert(rate > 0); if (r->i_ss.rate == rate) return; - + r->i_ss.rate = rate; - - if (r->impl_update_input_rate) - r->impl_update_input_rate(r, rate); + + r->impl_update_rates(r); } -void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) { - assert(r && in && out && r->impl_run); +void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) { + pa_assert(r); + pa_assert(rate > 0); - r->impl_run(r, in, out); + 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) { - assert(r); - + pa_assert(r); + return (((out_length / r->o_fz)*r->i_ss.rate)/r->o_ss.rate) * r->i_fz; } +size_t pa_resampler_result(pa_resampler *r, size_t in_length) { + pa_assert(r); + + return (((in_length / r->i_fz)*r->o_ss.rate)/r->i_ss.rate) * r->o_fz; +} + +size_t pa_resampler_max_block_size(pa_resampler *r) { + size_t block_size_max; + pa_sample_spec 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.channels = PA_MAX(r->i_ss.channels, r->o_ss.channels); + + /* We silently assume that the format enum is ordered by size */ + ss.format = PA_MAX(r->i_ss.format, r->o_ss.format); + ss.format = PA_MAX(ss.format, r->work_format); + + ss.rate = PA_MAX(r->i_ss.rate, r->o_ss.rate); + + fs = pa_frame_size(&ss); + + return (((block_size_max/fs - EXTRA_FRAMES)*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) { - assert(r); - return r->resample_method; + pa_assert(r); + + return r->method; } static const char * const resample_methods[] = { @@ -186,7 +405,33 @@ static const char * const resample_methods[] = { "src-sinc-fastest", "src-zero-order-hold", "src-linear", - "trivial" + "trivial", + "speex-float-0", + "speex-float-1", + "speex-float-2", + "speex-float-3", + "speex-float-4", + "speex-float-5", + "speex-float-6", + "speex-float-7", + "speex-float-8", + "speex-float-9", + "speex-float-10", + "speex-fixed-0", + "speex-fixed-1", + "speex-fixed-2", + "speex-fixed-3", + "speex-fixed-4", + "speex-fixed-5", + "speex-fixed-6", + "speex-fixed-7", + "speex-fixed-8", + "speex-fixed-9", + "speex-fixed-10", + "ffmpeg", + "auto", + "copy", + "peaks" }; const char *pa_resample_method_to_string(pa_resample_method_t m) { @@ -197,288 +442,718 @@ const char *pa_resample_method_to_string(pa_resample_method_t m) { 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; - - assert(string); + + 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) { -/*** libsamplerate based implementation ***/ + 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 void libsamplerate_free(pa_resampler *r) { - struct impl_libsamplerate *u; - - assert(r); - assert(r->impl_data); - - u = r->impl_data; - - if (u->src_state) - src_delete(u->src_state); - - if (u->buf1.memblock) - pa_memblock_unref(u->buf1.memblock); - if (u->buf2.memblock) - pa_memblock_unref(u->buf2.memblock); - if (u->buf3.memblock) - pa_memblock_unref(u->buf3.memblock); - if (u->buf4.memblock) - pa_memblock_unref(u->buf4.memblock); - pa_xfree(u); +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) { - struct impl_libsamplerate *u; - unsigned oc; - assert(r); - assert(r->impl_data); + unsigned oc, ic; + pa_bool_t ic_connected[PA_CHANNELS_MAX]; + pa_bool_t remix; + pa_strbuf *s; + char *t; - u = r->impl_data; + pa_assert(r); - if (!(u->map_required = (!pa_channel_map_equal(&r->i_cm, &r->o_cm) || r->i_ss.channels != r->o_ss.channels))) + 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++) { - unsigned ic, i = 0; + 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, b; - - a = r->i_cm.map[ic]; - b = r->o_cm.map[oc]; - - if (a == b || - (a == PA_CHANNEL_POSITION_MONO && b == PA_CHANNEL_POSITION_LEFT) || - (a == PA_CHANNEL_POSITION_MONO && b == PA_CHANNEL_POSITION_RIGHT) || - (a == PA_CHANNEL_POSITION_LEFT && b == PA_CHANNEL_POSITION_MONO) || - (a == PA_CHANNEL_POSITION_RIGHT && b == PA_CHANNEL_POSITION_MONO)) - - u->map_table[oc][i++] = 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; + } } - /* Add an end marker */ - if (i < PA_CHANNELS_MAX) - u->map_table[oc][i] = -1; + 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_float(pa_resampler *r, pa_memchunk *input) { - struct impl_libsamplerate *u; +static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) { unsigned n_samples; void *src, *dst; - assert(r); - assert(input); - assert(input->memblock); - - assert(r->impl_data); - u = r->impl_data; - - /* Convert the incoming sample into floats and place them in buf1 */ + pa_assert(r); + pa_assert(input); + pa_assert(input->memblock); - if (!u->to_float32ne_func || !input->length) + /* 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; - if (!u->buf1.memblock || u->buf1_samples < n_samples) { - if (u->buf1.memblock) - pa_memblock_unref(u->buf1.memblock); + 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); - u->buf1_samples = n_samples; - u->buf1.memblock = pa_memblock_new(r->mempool, u->buf1.length = sizeof(float) * n_samples); - u->buf1.index = 0; + 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(u->buf1.memblock); - u->to_float32ne_func(n_samples, src, dst); + 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(u->buf1.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; - u->buf1.length = sizeof(float) * n_samples; + i3 = (int32_t) (s3 * 0x10000); + i4 = (int32_t) (s4 * 0x10000); - return &u->buf1; + 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) { - struct impl_libsamplerate *u; - unsigned n_samples, n_frames; + unsigned in_n_samples, out_n_samples, n_frames; int i_skip, o_skip; unsigned oc; - float *src, *dst; - - assert(r); - assert(input); - assert(input->memblock); - - assert(r->impl_data); - u = r->impl_data; + void *src, *dst; + + pa_assert(r); + pa_assert(input); + pa_assert(input->memblock); /* Remap channels and place the result int buf2 */ - - if (!u->map_required || !input->length) + + if (!r->map_required || !input->length) return input; - n_samples = input->length / sizeof(float); - n_frames = n_samples / r->o_ss.channels; + 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; - if (!u->buf2.memblock || u->buf2_samples < n_samples) { - if (u->buf2.memblock) - pa_memblock_unref(u->buf2.memblock); + r->buf2.index = 0; + r->buf2.length = r->w_sz * out_n_samples; - u->buf2_samples = n_samples; - u->buf2.memblock = pa_memblock_new(r->mempool, u->buf2.length = sizeof(float) * n_samples); - u->buf2.index = 0; + 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 = (float*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); - dst = (float*) pa_memblock_acquire(u->buf2.memblock); - - memset(dst, 0, n_samples * sizeof(float)); + src = ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); + dst = pa_memblock_acquire(r->buf2.memblock); - o_skip = sizeof(float) * r->o_ss.channels; - i_skip = sizeof(float) * r->i_ss.channels; - - for (oc = 0; oc < r->o_ss.channels; oc++) { - unsigned i; - static const float one = 1.0; - - for (i = 0; i < PA_CHANNELS_MAX && u->map_table[oc][i] >= 0; i++) - oil_vectoradd_f32( - dst + oc, o_skip, - dst + oc, o_skip, - src + u->map_table[oc][i], i_skip, - n_frames, - &one, &one); + 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(u->buf2.memblock); + pa_memblock_release(r->buf2.memblock); - u->buf2.length = n_frames * sizeof(float) * r->o_ss.channels; + r->buf2.length = out_n_samples * r->w_sz; - return &u->buf2; + return &r->buf2; } static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) { - struct impl_libsamplerate *u; - SRC_DATA data; unsigned in_n_frames, in_n_samples; unsigned out_n_frames, out_n_samples; - int ret; - assert(r); - assert(input); - assert(r->impl_data); - u = r->impl_data; + pa_assert(r); + pa_assert(input); /* Resample the data and place the result in buf3 */ - - if (!u->src_state || !input->length) + + if (!r->impl_resample || !input->length) return input; - in_n_samples = input->length / sizeof(float); - in_n_frames = in_n_samples * r->o_ss.channels; + 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)+1024; + out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_FRAMES; out_n_samples = out_n_frames * r->o_ss.channels; - if (!u->buf3.memblock || u->buf3_samples < out_n_samples) { - if (u->buf3.memblock) - pa_memblock_unref(u->buf3.memblock); + r->buf3.index = 0; + r->buf3.length = r->w_sz * out_n_samples; - u->buf3_samples = out_n_samples; - u->buf3.memblock = pa_memblock_new(r->mempool, u->buf3.length = sizeof(float) * out_n_samples); - u->buf3.index = 0; - } - - data.data_in = (float*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); - data.input_frames = in_n_frames; + if (!r->buf3.memblock || r->buf3_samples < out_n_samples) { + if (r->buf3.memblock) + pa_memblock_unref(r->buf3.memblock); - data.data_out = (float*) pa_memblock_acquire(u->buf3.memblock); - data.output_frames = out_n_frames; - - data.src_ratio = (double) r->o_ss.rate / r->i_ss.rate; - data.end_of_input = 0; - - ret = src_process(u->src_state, &data); - assert(ret == 0); - assert((unsigned) data.input_frames_used == in_n_frames); + r->buf3_samples = out_n_samples; + r->buf3.memblock = pa_memblock_new(r->mempool, r->buf3.length); + } - pa_memblock_release(input->memblock); - pa_memblock_release(u->buf3.memblock); - - u->buf3.length = data.output_frames_gen * sizeof(float) * r->o_ss.channels; + 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 &u->buf3; + return &r->buf3; } -static pa_memchunk *convert_from_float(pa_resampler *r, pa_memchunk *input) { - struct impl_libsamplerate *u; +static pa_memchunk *convert_from_work_format(pa_resampler *r, pa_memchunk *input) { unsigned n_samples, n_frames; void *src, *dst; - - assert(r); - assert(input); - assert(r->impl_data); - u = r->impl_data; - + + pa_assert(r); + pa_assert(input); + /* Convert the data into the correct sample type and place the result in buf4 */ - if (!u->from_float32ne_func || !input->length) + if (!r->from_work_format_func || !input->length) return input; - n_frames = input->length / sizeof(float) / r->o_ss.channels; - n_samples = n_frames * r->o_ss.channels; + 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 (u->buf4_samples < n_samples) { - if (u->buf4.memblock) - pa_memblock_unref(u->buf4.memblock); + if (!r->buf4.memblock || r->buf4_samples < n_samples) { + if (r->buf4.memblock) + pa_memblock_unref(r->buf4.memblock); - u->buf4_samples = n_samples; - u->buf4.memblock = pa_memblock_new(r->mempool, u->buf4.length = r->o_fz * n_frames); - u->buf4.index = 0; + 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->length; - dst = pa_memblock_acquire(u->buf4.memblock); - u->from_float32ne_func(n_samples, src, dst); + 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(u->buf4.memblock); + pa_memblock_release(r->buf4.memblock); - u->buf4.length = r->o_fz * n_frames; - - return &u->buf4; + r->buf4.length = r->o_fz * n_frames; + + return &r->buf4; } -static void libsamplerate_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) { - struct impl_libsamplerate *u; +void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) { pa_memchunk *buf; - assert(r); - assert(in); - assert(out); - assert(in->length); - assert(in->memblock); - assert(in->length % r->i_fz == 0); - assert(r->impl_data); - - u = r->impl_data; - - buf = convert_to_float(r, (pa_memchunk*) in); + 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_float(r, buf); + buf = convert_from_work_format(r, buf); *out = *buf; if (buf == in) @@ -487,175 +1162,486 @@ static void libsamplerate_run(pa_resampler *r, const pa_memchunk *in, pa_memchun 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); - pa_memblock_release(in->memblock); - + 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_input_rate(pa_resampler *r, uint32_t rate) { - struct impl_libsamplerate *u; - - assert(r); - assert(rate > 0); - assert(r->impl_data); - u = r->impl_data; +static void libsamplerate_update_rates(pa_resampler *r) { + pa_assert(r); - if (!u->src_state) { - int err; - u->src_state = src_new(r->resample_method, r->o_ss.channels, &err); - assert(u->src_state); - } else { - int ret = src_set_ratio(u->src_state, (double) r->o_ss.rate / rate); - assert(ret == 0); - } + pa_assert_se(src_set_ratio(r->src.state, (double) r->o_ss.rate / r->i_ss.rate) == 0); } -static int libsamplerate_init(pa_resampler *r) { - struct impl_libsamplerate *u = NULL; - int err; +static void libsamplerate_reset(pa_resampler *r) { + pa_assert(r); - r->impl_data = u = pa_xnew(struct impl_libsamplerate, 1); + pa_assert_se(src_reset(r->src.state) == 0); +} - pa_memchunk_reset(&u->buf1); - pa_memchunk_reset(&u->buf2); - pa_memchunk_reset(&u->buf3); - pa_memchunk_reset(&u->buf4); - u->buf1_samples = u->buf2_samples = u->buf3_samples = u->buf4_samples = 0; +static void libsamplerate_free(pa_resampler *r) { + pa_assert(r); - if (r->i_ss.format == PA_SAMPLE_FLOAT32NE) - u->to_float32ne_func = NULL; - else if (!(u->to_float32ne_func = pa_get_convert_to_float32ne_function(r->i_ss.format))) - goto fail; + if (r->src.state) + src_delete(r->src.state); +} - if (r->o_ss.format == PA_SAMPLE_FLOAT32NE) - u->from_float32ne_func = NULL; - else if (!(u->from_float32ne_func = pa_get_convert_from_float32ne_function(r->o_ss.format))) - goto fail; +static int libsamplerate_init(pa_resampler *r) { + int err; - if (r->o_ss.rate == r->i_ss.rate) - u->src_state = NULL; - else if (!(u->src_state = src_new(r->resample_method, r->o_ss.channels, &err))) - goto fail; + 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_input_rate = libsamplerate_update_input_rate; - r->impl_run = libsamplerate_run; + r->impl_update_rates = libsamplerate_update_rates; + r->impl_resample = libsamplerate_resample; + r->impl_reset = libsamplerate_reset; - calc_map_table(r); - return 0; +} +#endif -fail: - pa_xfree(u); - return -1; +/*** 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_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) { +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 n_frames; - struct impl_trivial *u; + unsigned o_index; + void *src, *dst; - assert(r); - assert(in); - assert(out); - assert(r->impl_data); - - u = r->impl_data; + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); - fz = r->i_fz; - assert(fz == r->o_fz); + fz = r->w_sz * r->o_ss.channels; - n_frames = in->length/fz; + src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index; + dst = (uint8_t*) pa_memblock_acquire(output->memblock) + output->index; - if (r->i_ss.rate == r->o_ss.rate) { + for (o_index = 0;; o_index++, r->trivial.o_counter++) { + unsigned j; - /* In case there's no diefference in sample types, do nothing */ - *out = *in; - pa_memblock_ref(out->memblock); + 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; - u->o_counter += n_frames; - } else { - /* Do real resampling */ - size_t l; - unsigned o_index; - void *src, *dst; - - /* The length of the new memory block rounded up */ - l = ((((n_frames+1) * r->o_ss.rate) / r->i_ss.rate) + 1) * fz; - - out->index = 0; - out->memblock = pa_memblock_new(r->mempool, l); - - src = (uint8_t*) pa_memblock_acquire(in->memblock) + in->index; - dst = pa_memblock_acquire(out->memblock); - - for (o_index = 0;; o_index++, u->o_counter++) { - unsigned j; - - j = (u->o_counter * r->i_ss.rate / r->o_ss.rate); - j = j > u->i_counter ? j - u->i_counter : 0; - - if (j >= n_frames) - break; - - assert(o_index*fz < pa_memblock_get_length(out->memblock)); - - memcpy((uint8_t*) dst + fz*o_index, - (uint8_t*) src + fz*j, fz); - + 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; +} + +/* Peak finder implementation */ + +static void peaks_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; + unsigned start = 0; + + 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->peaks.o_counter++) { + unsigned j; + + j = ((r->peaks.o_counter * r->i_ss.rate) / r->o_ss.rate); + j = j > r->peaks.i_counter ? j - r->peaks.i_counter : 0; + + if (j >= in_n_frames) + break; + + pa_assert(o_index * fz < pa_memblock_get_length(output->memblock)); + + if (r->work_format == PA_SAMPLE_S16NE) { + unsigned i, c; + int16_t *s = (int16_t*) ((uint8_t*) src + fz * j); + int16_t *d = (int16_t*) ((uint8_t*) dst + fz * o_index); + + for (i = start; i <= j; i++) + for (c = 0; c < r->o_ss.channels; c++, s++) { + int16_t n; + + n = *s < 0 ? -*s : *s; + + if (n > r->peaks.max_i[c]) + r->peaks.max_i[c] = n; + } + + for (c = 0; c < r->o_ss.channels; c++, d++) { + *d = r->peaks.max_i[c]; + r->peaks.max_i[c] = 0; + } + } else { + unsigned i, c; + float *s = (float*) ((uint8_t*) src + fz * j); + float *d = (float*) ((uint8_t*) dst + fz * o_index); + + pa_assert(r->work_format == PA_SAMPLE_FLOAT32NE); + + for (i = start; i <= j; i++) + for (c = 0; c < r->o_ss.channels; c++, s++) { + float n = fabsf(*s); + + if (n > r->peaks.max_f[c]) + r->peaks.max_f[c] = n; + } + + for (c = 0; c < r->o_ss.channels; c++, d++) { + *d = r->peaks.max_f[c]; + r->peaks.max_f[c] = 0; + } } - pa_memblock_release(in->memblock); - pa_memblock_release(out->memblock); - - out->length = o_index*fz; + start = j+1; } - u->i_counter += n_frames; - + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + *out_n_frames = o_index; + + r->peaks.i_counter += in_n_frames; + /* Normalize counters */ - while (u->i_counter >= r->i_ss.rate) { - u->i_counter -= r->i_ss.rate; - assert(u->o_counter >= r->o_ss.rate); - u->o_counter -= r->o_ss.rate; + while (r->peaks.i_counter >= r->i_ss.rate) { + pa_assert(r->peaks.o_counter >= r->o_ss.rate); + + r->peaks.i_counter -= r->i_ss.rate; + r->peaks.o_counter -= r->o_ss.rate; } } -static void trivial_free(pa_resampler *r) { - assert(r); - - pa_xfree(r->impl_data); +static void peaks_update_rates_or_reset(pa_resampler *r) { + pa_assert(r); + + r->peaks.i_counter = 0; + r->peaks.o_counter = 0; +} + +static int peaks_init(pa_resampler*r) { + pa_assert(r); + + r->peaks.o_counter = r->peaks.i_counter = 0; + memset(r->peaks.max_i, 0, sizeof(r->peaks.max_i)); + memset(r->peaks.max_f, 0, sizeof(r->peaks.max_f)); + + r->impl_resample = peaks_resample; + r->impl_update_rates = peaks_update_rates_or_reset; + r->impl_reset = peaks_update_rates_or_reset; + + return 0; } -static void trivial_update_input_rate(pa_resampler *r, uint32_t rate) { - struct impl_trivial *u; +/*** 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]); + } - assert(r); - assert(rate > 0); - assert(r->impl_data); + /* 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); + } - u = r->impl_data; - u->i_counter = 0; - u->o_counter = 0; + *out_n_frames = used_frames; } -static int trivial_init(pa_resampler*r) { - struct impl_trivial *u; - - assert(r); - assert(r->i_ss.format == r->o_ss.format); - assert(r->i_ss.channels == r->o_ss.channels); - - r->impl_data = u = pa_xnew(struct impl_trivial, 1); - u->o_counter = u->i_counter = 0; - - r->impl_run = trivial_run; - r->impl_free = trivial_free; - r->impl_update_input_rate = trivial_update_input_rate; - +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 index 327e24a2..5e302a9b 100644 --- a/src/pulsecore/resampler.h +++ b/src/pulsecore/resampler.h @@ -1,29 +1,27 @@ #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 <samplerate.h> - #include <pulse/sample.h> #include <pulse/channelmap.h> #include <pulsecore/memblock.h> @@ -33,34 +31,61 @@ typedef struct pa_resampler pa_resampler; typedef enum pa_resample_method { PA_RESAMPLER_INVALID = -1, - PA_RESAMPLER_SRC_SINC_BEST_QUALITY = SRC_SINC_BEST_QUALITY, - PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY = SRC_SINC_MEDIUM_QUALITY, - PA_RESAMPLER_SRC_SINC_FASTEST = SRC_SINC_FASTEST, - PA_RESAMPLER_SRC_ZERO_ORDER_HOLD = SRC_ZERO_ORDER_HOLD, - PA_RESAMPLER_SRC_LINEAR = SRC_LINEAR, + PA_RESAMPLER_SRC_SINC_BEST_QUALITY = 0, /* = SRC_SINC_BEST_QUALITY */ + PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY = 1, /* = SRC_SINC_MEDIUM_QUALITY */ + PA_RESAMPLER_SRC_SINC_FASTEST = 2, /* = SRC_SINC_FASTEST */ + PA_RESAMPLER_SRC_ZERO_ORDER_HOLD = 3, /* = SRC_ZERO_ORDER_HOLD */ + PA_RESAMPLER_SRC_LINEAR = 4, /* = SRC_LINEAR */ PA_RESAMPLER_TRIVIAL, + PA_RESAMPLER_SPEEX_FLOAT_BASE, + PA_RESAMPLER_SPEEX_FLOAT_MAX = PA_RESAMPLER_SPEEX_FLOAT_BASE + 10, + PA_RESAMPLER_SPEEX_FIXED_BASE, + PA_RESAMPLER_SPEEX_FIXED_MAX = PA_RESAMPLER_SPEEX_FIXED_BASE + 10, + PA_RESAMPLER_FFMPEG, + PA_RESAMPLER_AUTO, /* automatic select based on sample format */ + PA_RESAMPLER_COPY, + PA_RESAMPLER_PEAKS, PA_RESAMPLER_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_method_t resample_method, + pa_resample_flags_t flags); void pa_resampler_free(pa_resampler *r); /* Returns the size of an input memory block which is required to return the specified amount of output data */ size_t pa_resampler_request(pa_resampler *r, size_t out_length); +/* Inverse of pa_resampler_request() */ +size_t pa_resampler_result(pa_resampler *r, size_t in_length); + +/* Returns the maximum size of input blocks we can process without needing bounce buffers larger than the mempool tile size. */ +size_t pa_resampler_max_block_size(pa_resampler *r); + /* Pass the specified memory chunk to the resampler and return the newly resampled data */ void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out); /* Change the input rate of the resampler object */ void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate); +/* Change the output rate of the resampler object */ +void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate); + +/* Reinitialize state of the resampler, possibly due to seeking or other discontinuities */ +void pa_resampler_reset(pa_resampler *r); + /* Return the resampling method of the resampler object */ pa_resample_method_t pa_resampler_get_method(pa_resampler *r); @@ -70,4 +95,8 @@ 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..f33de830 --- /dev/null +++ b/src/pulsecore/rtclock.c @@ -0,0 +1,117 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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)); +} + +struct timeval* pa_rtclock_from_wallclock(struct timeval *tv) { + +#ifdef HAVE_CLOCK_GETTIME + struct timeval wc_now, rt_now; + + pa_gettimeofday(&wc_now); + pa_rtclock_get(&rt_now); + + pa_assert(tv); + + if (pa_timeval_cmp(&wc_now, tv) < 0) + pa_timeval_add(&rt_now, pa_timeval_diff(tv, &wc_now)); + else + pa_timeval_sub(&rt_now, pa_timeval_diff(&wc_now, tv)); + + *tv = rt_now; +#endif + + return tv; +} diff --git a/src/pulsecore/rtclock.h b/src/pulsecore/rtclock.h new file mode 100644 index 00000000..705de48f --- /dev/null +++ b/src/pulsecore/rtclock.h @@ -0,0 +1,43 @@ +#ifndef foopulsertclockhfoo +#define foopulsertclockhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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 + +struct timeval* pa_rtclock_from_wallclock(struct timeval *tv); + +#endif diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c new file mode 100644 index 00000000..a67a5516 --- /dev/null +++ b/src/pulsecore/rtpoll.c @@ -0,0 +1,766 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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_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; + pa_bool_t timer_armed; +#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) { /* write(2, "signal\n", 7); */ } + +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, µ) != 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; + p->timer_armed = FALSE; + +#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; + + 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); + + memset(&timeout, 0, sizeof(timeout)); + + /* Calculate timeout */ + if (wait && !p->quit && p->timer_enabled) { + struct timeval now; + pa_rtclock_get(&now); + + if (pa_timeval_cmp(&p->next_elapse, &now) > 0) + pa_timeval_add(&timeout, pa_timeval_diff(&p->next_elapse, &now)); + } + + /* 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, (!wait || p->quit || 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, (!wait || p->quit || 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); + } + + /* 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; + struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; + sigset_t ss; + + if (p->timer_armed) { + /* First disarm timer */ + memset(&its, 0, sizeof(its)); + pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0); + + /* Remove a signal that might be waiting in the signal q */ + pa_assert_se(sigemptyset(&ss) == 0); + pa_assert_se(sigaddset(&ss, p->rtsig) == 0); + sigtimedwait(&ss, NULL, &ts); + } + + /* And install the new timer */ + if (p->timer_enabled) { + memset(&its, 0, sizeof(its)); + + 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 == 0) + its.it_value.tv_nsec = 1; + pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0); + } + + p->timer_armed = p->timer_enabled; + } + +#ifdef __linux__ + } +#endif + +#endif +} + +void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec) { + pa_assert(p); + + pa_timeval_store(&p->next_elapse, usec); + p->timer_enabled = TRUE; + + update_timer(p); +} + +void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) { + pa_assert(p); + + /* Scheduling a timeout for more than an hour is very very suspicious */ + pa_assert(usec <= PA_USEC_PER_SEC*60ULL*60ULL); + + pa_rtclock_get(&p->next_elapse); + pa_timeval_add(&p->next_elapse, usec); + p->timer_enabled = TRUE; + + update_timer(p); +} + +void pa_rtpoll_set_timer_disabled(pa_rtpoll *p) { + pa_assert(p); + + memset(&p->next_elapse, 0, sizeof(p->next_elapse)); + p->timer_enabled = FALSE; + + 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_read_before(pa_rtpoll_item *i) { + pa_assert(i); + + if (pa_asyncmsgq_read_before_poll(i->userdata) < 0) + return 1; /* 1 means immediate restart of the loop */ + + return 0; +} + +static void asyncmsgq_read_after(pa_rtpoll_item *i) { + pa_assert(i); + + pa_assert((i->pollfd[0].revents & ~POLLIN) == 0); + pa_asyncmsgq_read_after_poll(i->userdata); +} + +static int asyncmsgq_read_work(pa_rtpoll_item *i) { + pa_msgobject *object; + int code; + void *data; + pa_memchunk chunk; + int64_t offset; + + pa_assert(i); + + if (pa_asyncmsgq_get(i->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_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) { + pa_rtpoll_item *i; + struct pollfd *pollfd; + + pa_assert(p); + pa_assert(q); + + i = pa_rtpoll_item_new(p, prio, 1); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + pollfd->fd = pa_asyncmsgq_read_fd(q); + pollfd->events = POLLIN; + + i->before_cb = asyncmsgq_read_before; + i->after_cb = asyncmsgq_read_after; + i->work_cb = asyncmsgq_read_work; + i->userdata = q; + + return i; +} + +static int asyncmsgq_write_before(pa_rtpoll_item *i) { + pa_assert(i); + + pa_asyncmsgq_write_before_poll(i->userdata); + return 0; +} + +static void asyncmsgq_write_after(pa_rtpoll_item *i) { + pa_assert(i); + + pa_assert((i->pollfd[0].revents & ~POLLIN) == 0); + pa_asyncmsgq_write_after_poll(i->userdata); +} + +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) { + pa_rtpoll_item *i; + struct pollfd *pollfd; + + pa_assert(p); + pa_assert(q); + + i = pa_rtpoll_item_new(p, prio, 1); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + pollfd->fd = pa_asyncmsgq_write_fd(q); + pollfd->events = POLLIN; + + i->before_cb = asyncmsgq_write_before; + i->after_cb = asyncmsgq_write_after; + i->work_cb = NULL; + 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..08776ef0 --- /dev/null +++ b/src/pulsecore/rtpoll.h @@ -0,0 +1,114 @@ +#ifndef foopulsertpollhfoo +#define foopulsertpollhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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, 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_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q); +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q); + +/* 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..4df217c3 --- /dev/null +++ b/src/pulsecore/rtsig.c @@ -0,0 +1,131 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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..e414122d --- /dev/null +++ b/src/pulsecore/rtsig.h @@ -0,0 +1,39 @@ +#ifndef foopulsertsighfoo +#define foopulsertsighfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 index 52023d31..b42b79d1 100644 --- a/src/pulsecore/sample-util.c +++ b/src/pulsecore/sample-util.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,72 +26,127 @@ #include <stdio.h> #include <string.h> -#include <assert.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" -pa_memblock *pa_silence_memblock_new(pa_mempool *pool, const pa_sample_spec *spec, size_t length) { - assert(pool); - assert(spec); - - if (length == 0) - length = pa_bytes_per_second(spec)/20; /* 50 ms */ - - return pa_silence_memblock(pa_memblock_new(pool, length), spec); -} +#define PA_SILENCE_MAX (PA_PAGE_SIZE*16) pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec) { void *data; - assert(b); - assert(spec); - + 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) { +pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec) { void *data; - - assert(c); - assert(c->memblock); - assert(spec); + + 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; - assert(p && length && spec); + return c; +} - switch (spec->format) { +static uint8_t silence_byte(pa_sample_format_t format) { + switch (format) { case PA_SAMPLE_U8: - c = 0x80; - break; + return 0x80; case PA_SAMPLE_S16LE: case PA_SAMPLE_S16BE: - case PA_SAMPLE_FLOAT32: - c = 0; - break; + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + return 0; case PA_SAMPLE_ALAW: + return 0xd5; case PA_SAMPLE_ULAW: - c = 80; - break; + return 0xff; default: - assert(0); + pa_assert_not_reached(); + } + return 0; +} + +void* pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) { + pa_assert(p); + pa_assert(length > 0); + pa_assert(spec); + + memset(p, silence_byte(spec->format), length); + return p; +} + +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]); + } } - - memset(p, c, length); +} + +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( @@ -100,226 +156,442 @@ size_t pa_mix( size_t length, const pa_sample_spec *spec, const pa_cvolume *volume, - int mute) { + pa_bool_t mute) { pa_cvolume full_volume; - size_t d = 0; unsigned k; - - assert(streams); - assert(data); - assert(length); - assert(spec); + 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].internal = pa_memblock_acquire(streams[k].chunk.memblock); - + 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; - - if (d >= length) + unsigned i; + + if (PA_UNLIKELY(d >= length)) goto finish; - if (!mute && volume->values[channel] != PA_VOLUME_MUTED) { - unsigned i; - - for (i = 0; i < nstreams; i++) { - int32_t v; - pa_volume_t cvolume = streams[i].volume.values[channel]; - - if (d >= streams[i].chunk.length) - goto finish; - - if (cvolume == PA_VOLUME_MUTED) - v = 0; - else { - v = *((int16_t*) ((uint8_t*) streams[i].internal + streams[i].chunk.index + d)); - - if (cvolume != PA_VOLUME_NORM) - v = (int32_t) (v * pa_sw_volume_to_linear(cvolume)); - } - - sum += v; - } - - if (volume->values[channel] != PA_VOLUME_NORM) - sum = (int32_t) (sum * pa_sw_volume_to_linear(volume->values[channel])); + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; - if (sum < -0x8000) sum = -0x8000; - if (sum > 0x7FFF) sum = 0x7FFF; + 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); } - - *((int16_t*) data) = sum; + + 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 (++channel >= spec->channels) + + 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; - - if (d >= length) + unsigned i; + + if (PA_UNLIKELY(d >= length)) goto finish; - if (!mute && volume->values[channel] != PA_VOLUME_MUTED) { - unsigned i; - - for (i = 0; i < nstreams; i++) { - int32_t v; - pa_volume_t cvolume = streams[i].volume.values[channel]; - - if (d >= streams[i].chunk.length) - goto finish; - - if (cvolume == PA_VOLUME_MUTED) - v = 0; - else { - v = INT16_SWAP(*((int16_t*) ((uint8_t*) streams[i].internal + streams[i].chunk.index + d))); - - if (cvolume != PA_VOLUME_NORM) - v = (int32_t) (v * pa_sw_volume_to_linear(cvolume)); - } - - sum += v; - } - - if (volume->values[channel] != PA_VOLUME_NORM) - sum = (int32_t) (sum * pa_sw_volume_to_linear(volume->values[channel])); + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; - if (sum < -0x8000) sum = -0x8000; - if (sum > 0x7FFF) sum = 0x7FFF; + 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); } - - *((int16_t*) data) = INT16_SWAP(sum); + + 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 (++channel >= spec->channels) + + 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; - - if (d >= length) + unsigned i; + + if (PA_UNLIKELY(d >= length)) goto finish; - if (!mute && volume->values[channel] != PA_VOLUME_MUTED) { - unsigned i; - - for (i = 0; i < nstreams; i++) { - int32_t v; - pa_volume_t cvolume = streams[i].volume.values[channel]; - - if (d >= streams[i].chunk.length) - goto finish; - - if (cvolume == PA_VOLUME_MUTED) - v = 0; - else { - v = (int32_t) *((uint8_t*) streams[i].internal + streams[i].chunk.index + d) - 0x80; - - if (cvolume != PA_VOLUME_NORM) - v = (int32_t) (v * pa_sw_volume_to_linear(cvolume)); - } - - sum += v; - } + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; - if (volume->values[channel] != PA_VOLUME_NORM) - sum = (int32_t) (sum * pa_sw_volume_to_linear(volume->values[channel])); + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; - if (sum < -0x80) sum = -0x80; - if (sum > 0x7F) sum = 0x7F; + 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 (++channel >= spec->channels) + + 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; - - if (d >= length) + unsigned i; + + if (PA_UNLIKELY(d >= length)) goto finish; - - if (!mute && volume->values[channel] != PA_VOLUME_MUTED) { - unsigned i; - - for (i = 0; i < nstreams; i++) { - float v; - pa_volume_t cvolume = streams[i].volume.values[channel]; - - if (d >= streams[i].chunk.length) - goto finish; - - if (cvolume == PA_VOLUME_MUTED) - v = 0; - else { - v = *((float*) ((uint8_t*) streams[i].internal + streams[i].chunk.index + d)); - - if (cvolume != PA_VOLUME_NORM) - v *= pa_sw_volume_to_linear(cvolume); - } - - sum += v; + + 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; } - - if (volume->values[channel] != PA_VOLUME_NORM) - sum *= pa_sw_volume_to_linear(volume->values[channel]); + + sum += v; + m->ptr = (uint8_t*) m->ptr + sizeof(float); } - + + sum *= linear[channel]; *((float*) data) = sum; + data = (uint8_t*) data + sizeof(float); - if (++channel >= spec->channels) + 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)); - abort(); + pa_assert_not_reached(); } finish: for (k = 0; k < nstreams; k++) pa_memblock_release(streams[k].chunk.memblock); - + return d; } @@ -330,11 +602,14 @@ void pa_volume_memchunk( const pa_cvolume *volume) { void *ptr; - - assert(c); - assert(spec); - assert(c->length % pa_frame_size(spec) == 0); - assert(volume); + + pa_assert(c); + pa_assert(spec); + pa_assert(c->length % pa_frame_size(spec) == 0); + pa_assert(volume); + + if (pa_memblock_is_silence(c->memblock)) + return; if (pa_cvolume_channels_equal_to(volume, PA_VOLUME_NORM)) return; @@ -344,29 +619,27 @@ void pa_volume_memchunk( return; } - ptr = pa_memblock_acquire(c->memblock); + 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; - double linear[PA_CHANNELS_MAX]; - - for (channel = 0; channel < spec->channels; channel++) - linear[channel] = pa_sw_volume_to_linear(volume->values[channel]); - - for (channel = 0, d = (int16_t*) ((uint8_t*) ptr + c->index), n = c->length/sizeof(int16_t); n > 0; d++, n--) { - int32_t t = (int32_t)(*d); - - t = (int32_t) (t * linear[channel]); - - if (t < -0x8000) t = -0x8000; - if (t > 0x7FFF) t = 0x7FFF; - + 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 (++channel >= spec->channels) + + if (PA_UNLIKELY(++channel >= spec->channels)) channel = 0; } break; @@ -376,79 +649,379 @@ void pa_volume_memchunk( int16_t *d; size_t n; unsigned channel; - double linear[PA_CHANNELS_MAX]; - - for (channel = 0; channel < spec->channels; channel++) - linear[channel] = pa_sw_volume_to_linear(volume->values[channel]); - - for (channel = 0, d = (int16_t*) ((uint8_t*) ptr + c->index), n = c->length/sizeof(int16_t); n > 0; d++, n--) { - int32_t t = (int32_t)(INT16_SWAP(*d)); - - t = (int32_t) (t * linear[channel]); - - if (t < -0x8000) t = -0x8000; - if (t > 0x7FFF) t = 0x7FFF; - - *d = INT16_SWAP((int16_t) t); - - if (++channel >= spec->channels) + 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 = 0; - - for (d = (uint8_t*) ptr + c->index, n = c->length; n > 0; d++, n--) { - int32_t t = (int32_t) *d - 0x80; - - t = (int32_t) (t * pa_sw_volume_to_linear(volume->values[channel])); - - if (t < -0x80) t = -0x80; - if (t > 0x7F) t = 0x7F; - + 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 (++channel >= spec->channels) + + 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 = (float*) ((uint8_t*) ptr + c->index); + + d = ptr; skip = spec->channels * sizeof(float); n = c->length/sizeof(float)/spec->channels; - - for (channel = 0; channel < spec->channels ; channel ++) { + + for (channel = 0; channel < spec->channels; channel ++) { float v, *t; - - if (volume->values[channel] == PA_VOLUME_NORM) + + 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_error("ERROR: Unable to change volume of format %s.", - pa_sample_format_to_string(spec->format)); - abort(); + 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; + } + } +} + +static pa_memblock *silence_memblock_new(pa_mempool *pool, uint8_t c) { + pa_memblock *b; + size_t length; + void *data; + + pa_assert(pool); + + length = PA_MIN(pa_mempool_block_size_max(pool), PA_SILENCE_MAX); + + b = pa_memblock_new(pool, length); + + data = pa_memblock_acquire(b); + memset(data, c, length); + pa_memblock_release(b); + + pa_memblock_set_is_silence(b, TRUE); + + return b; +} + +void pa_silence_cache_init(pa_silence_cache *cache) { + pa_assert(cache); + + memset(cache, 0, sizeof(pa_silence_cache)); +} + +void pa_silence_cache_done(pa_silence_cache *cache) { + pa_sample_format_t f; + pa_assert(cache); + + for (f = 0; f < PA_SAMPLE_MAX; f++) + if (cache->blocks[f]) + pa_memblock_unref(cache->blocks[f]); + + memset(cache, 0, sizeof(pa_silence_cache)); +} + +pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length) { + pa_memblock *b; + size_t l; + + pa_assert(cache); + pa_assert(pa_sample_spec_valid(spec)); + + if (!(b = cache->blocks[spec->format])) + + switch (spec->format) { + case PA_SAMPLE_U8: + cache->blocks[PA_SAMPLE_U8] = b = silence_memblock_new(pool, 0x80); + break; + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0); + cache->blocks[PA_SAMPLE_S16BE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S32LE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b); + break; + case PA_SAMPLE_ALAW: + cache->blocks[PA_SAMPLE_ALAW] = b = silence_memblock_new(pool, 0xd5); + break; + case PA_SAMPLE_ULAW: + cache->blocks[PA_SAMPLE_ULAW] = b = silence_memblock_new(pool, 0xff); + break; + default: + pa_assert_not_reached(); + } + + pa_assert(b); + + ret->memblock = pa_memblock_ref(b); + + l = pa_memblock_get_length(b); + if (length > l || length == 0) + length = l; + + ret->length = pa_frame_align(length, spec); + ret->index = 0; + + return ret; +} + +void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n) { + const float *s; + float *d; + + s = src; d = dst; + + if (format == PA_SAMPLE_FLOAT32NE) { + + float minus_one = -1.0, plus_one = 1.0; + oil_clip_f32(d, dstr, s, sstr, n, &minus_one, &plus_one); + + } else { + pa_assert(format == PA_SAMPLE_FLOAT32RE); + + for (; n > 0; n--) { + float f; + + f = PA_FLOAT32_SWAP(*s); + f = PA_CLAMP_UNLIKELY(f, -1.0, 1.0); + *d = PA_FLOAT32_SWAP(f); + + s = (const float*) ((const uint8_t*) s + sstr); + d = (float*) ((uint8_t*) d + dstr); + } + } +} diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h index 04c2f6b1..cef70750 100644 --- a/src/pulsecore/sample-util.h +++ b/src/pulsecore/sample-util.h @@ -1,21 +1,22 @@ #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 @@ -27,16 +28,31 @@ #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_silence_cache { + pa_memblock* blocks[PA_SAMPLE_MAX]; +} pa_silence_cache; + +void pa_silence_cache_init(pa_silence_cache *cache); +void pa_silence_cache_done(pa_silence_cache *cache); + +void *pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec); +pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec); +pa_memblock* pa_silence_memblock(pa_memblock *b, const pa_sample_spec *spec); + +pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length); typedef struct pa_mix_info { pa_memchunk chunk; pa_cvolume volume; void *userdata; - void *internal; /* Used internally by pa_mix(), should not be initialised when calling pa_mix() */ + + /* 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( @@ -46,11 +62,20 @@ size_t pa_mix( size_t length, const pa_sample_spec *spec, const pa_cvolume *volume, - int mute); + 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); + +void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n); + #endif diff --git a/src/pulsecore/sconv-s16be.c b/src/pulsecore/sconv-s16be.c index 5ac96320..e7e1449a 100644 --- a/src/pulsecore/sconv-s16be.c +++ b/src/pulsecore/sconv-s16be.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -25,12 +25,30 @@ #include "endianmacros.h" -#define INT16_FROM INT16_FROM_BE -#define INT16_TO INT16_TO_BE +#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 diff --git a/src/pulsecore/sconv-s16be.h b/src/pulsecore/sconv-s16be.h index bd3fd345..60580815 100644 --- a/src/pulsecore/sconv-s16be.h +++ b/src/pulsecore/sconv-s16be.h @@ -1,28 +1,59 @@ #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. ***/ -void pa_sconv_s16be_to_float32ne(unsigned n, const void *a, float *b); -void pa_sconv_s16be_from_float32ne(unsigned n, const float *a, void *b); +#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 index d8b93cbd..41670f27 100644 --- a/src/pulsecore/sconv-s16le.c +++ b/src/pulsecore/sconv-s16le.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,12 +23,15 @@ #include <config.h> #endif -#include <assert.h> +/* 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" @@ -36,11 +39,19 @@ #include "sconv-s16le.h" #ifndef INT16_FROM -#define INT16_FROM INT16_FROM_LE +#define INT16_FROM PA_INT16_FROM_LE #endif #ifndef INT16_TO -#define INT16_TO INT16_TO_LE +#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 @@ -51,53 +62,188 @@ #endif #endif -void pa_sconv_s16le_to_float32ne(unsigned n, const void *a, float *b) { - const int16_t *ca = a; - - assert(a); - assert(b); +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 = *(ca++); + 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, ca, n, &add, &factor); + 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, void *b) { - int16_t *cb = b; - - assert(a); - assert(b); +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++); - if (v > 1) - v = 1; - - if (v < -1) - v = -1; - + v = PA_CLAMP_UNLIKELY(v, -1, 1); s = (int16_t) (v * 0x7FFF); - *(cb++) = INT16_TO(s); + *(b++) = INT16_TO(s); } #else { static const double add = 0, factor = 0x7FFF; - oil_scaleconv_s16_f32(cb, a, n, &add, &factor); + 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 index ae6e22d2..8d4c87c3 100644 --- a/src/pulsecore/sconv-s16le.h +++ b/src/pulsecore/sconv-s16le.h @@ -1,28 +1,59 @@ #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. ***/ -void pa_sconv_s16le_to_float32ne(unsigned n, const void *a, float *b); -void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, void *b); +#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 index ff2a0110..581e4681 100644 --- a/src/pulsecore/sconv.c +++ b/src/pulsecore/sconv.c @@ -1,18 +1,19 @@ -/* $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 @@ -25,12 +26,12 @@ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <liboil/liboilfuncs.h> #include <liboil/liboil.h> #include <pulsecore/g711.h> +#include <pulsecore/macro.h> #include "endianmacros.h" #include "sconv-s16le.h" @@ -38,132 +39,231 @@ #include "sconv.h" -static void u8_to_float32ne(unsigned n, const void *a, float *b) { - const uint8_t *ca = a; - static const double add = -128.0/127.0, factor = 1.0/127.0; - - assert(a); - assert(b); +/* 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; - oil_scaleconv_f32_u8(b, ca, n, &add, &factor); -} + pa_assert(a); + pa_assert(b); + + oil_scaleconv_u8_f32(b, a, n, &add, &factor); +} -static void u8_from_float32ne(unsigned n, const float *a, void *b) { - uint8_t *cb = b; - static const double add = 128.0, factor = 127.0; +static void u8_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { + static const int16_t add = -0x80, factor = 0x100; - assert(a); - assert(b); + pa_assert(a); + pa_assert(b); - oil_scaleconv_u8_f32(cb, a, n, &add, &factor); + 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 float32ne_to_float32ne(unsigned n, const void *a, float *b) { - assert(a); - assert(b); +static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { - oil_memcpy(b, a, sizeof(float) * n); + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = (uint8_t) (*a / 0x100 + 0x80); } -static void float32ne_from_float32ne(unsigned n, const float *a, void *b) { - assert(a); - assert(b); +/* 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 ulaw_to_float32ne(unsigned n, const void *a, float *b) { - const uint8_t *ca = a; +static void float32re_to_float32ne(unsigned n, const float *a, float *b) { + pa_assert(a); + pa_assert(b); - assert(a); - assert(b); - - for (; n > 0; n--) - *(b++) = st_ulaw2linear16(*(ca++)) * 1.0F / 0x7FFF; + for (; n > 0; n--, a++, b++) + *((uint32_t *) b) = PA_UINT32_SWAP(*((uint32_t *) a)); } -static void ulaw_from_float32ne(unsigned n, const float *a, void *b) { - uint8_t *cb = b; +/* s16 */ - assert(a); - assert(b); - - for (; n > 0; n--) { - float v = *(a++); +static void s16ne_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); - if (v > 1) - v = 1; + oil_memcpy(b, a, sizeof(int16_t) * n); +} - if (v < -1) - v = -1; +static void s16re_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); - *(cb++) = st_14linear2ulaw((int16_t) (v * 0x1FFF)); - } + for (; n > 0; n--, a++, b++) + *b = PA_UINT16_SWAP(*a); } -static void alaw_to_float32ne(unsigned n, const void *a, float *b) { - const uint8_t *ca = a; +/* ulaw */ - assert(a); - assert(b); +static void ulaw_to_float32ne(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); for (; n > 0; n--) - *(b++) = st_alaw2linear16(*(ca++)) * 1.0F / 0x7FFF; + *(b++) = (float) st_ulaw2linear16(*(a++)) / 0x8000; } -static void alaw_from_float32ne(unsigned n, const float *a, void *b) { - uint8_t *cb = b; +static void ulaw_from_float32ne(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); - assert(a); - assert(b); - for (; n > 0; n--) { float v = *(a++); + v = PA_CLAMP_UNLIKELY(v, -1, 1); + v *= 0x1FFF; + *(b++) = st_14linear2ulaw((int16_t) v); + } +} - if (v > 1) - v = 1; +static void ulaw_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); - if (v < -1) - v = -1; + for (; n > 0; n--, a++, b++) + *b = st_ulaw2linear16(*a); +} - *(cb++) = st_13linear2alaw((int16_t) (v * 0xFFF)); - } +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); } -pa_convert_to_float32ne_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) { - switch(f) { - case PA_SAMPLE_U8: - return u8_to_float32ne; - case PA_SAMPLE_S16LE: - return pa_sconv_s16le_to_float32ne; - case PA_SAMPLE_S16BE: - return pa_sconv_s16be_to_float32ne; - case PA_SAMPLE_FLOAT32NE: - return float32ne_to_float32ne; - case PA_SAMPLE_ALAW: - return alaw_to_float32ne; - case PA_SAMPLE_ULAW: - return ulaw_to_float32ne; - default: - return NULL; - } +/* 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; } -pa_convert_from_float32ne_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) { - switch(f) { - case PA_SAMPLE_U8: - return u8_from_float32ne; - case PA_SAMPLE_S16LE: - return pa_sconv_s16le_from_float32ne; - case PA_SAMPLE_S16BE: - return pa_sconv_s16be_from_float32ne; - case PA_SAMPLE_FLOAT32NE: - return float32ne_from_float32ne; - case PA_SAMPLE_ALAW: - return alaw_from_float32ne; - case PA_SAMPLE_ULAW: - return ulaw_from_float32ne; - default: - return NULL; +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 index 4aba0694..59710369 100644 --- a/src/pulsecore/sconv.h +++ b/src/pulsecore/sconv.h @@ -1,21 +1,22 @@ #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 @@ -24,10 +25,12 @@ #include <pulse/sample.h> -typedef void (*pa_convert_to_float32ne_func_t)(unsigned n, const void *a, float *b); -typedef void (*pa_convert_from_float32ne_func_t)(unsigned n, const float *a, void *b); +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_to_float32ne_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f); -pa_convert_from_float32ne_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f); +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..7c9f859d --- /dev/null +++ b/src/pulsecore/semaphore-posix.c @@ -0,0 +1,67 @@ +/*** + 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..41e0b8d4 --- /dev/null +++ b/src/pulsecore/semaphore-win32.c @@ -0,0 +1,63 @@ +/*** + 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/anotify.h b/src/pulsecore/semaphore.h index 44e942f7..850ae817 100644 --- a/src/pulsecore/anotify.h +++ b/src/pulsecore/semaphore.h @@ -1,38 +1,33 @@ -#ifndef foopulseanotifyhfoo -#define foopulseanotifyhfoo - -/* $Id$ */ +#ifndef foopulsesemaphorehfoo +#define foopulsesemaphorehfoo /*** This file is part of PulseAudio. - + + Copyright 2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ -/* Asynchronous thread-safe notification of mainloops */ - - -#include <inttypes.h> -#include <pulse/mainloop-api.h> +typedef struct pa_semaphore pa_semaphore; -typedef struct pa_anotify pa_anotify; -typedef void (*pa_anotify_cb_t)(uint8_t event, void *userdata); +pa_semaphore* pa_semaphore_new(unsigned value); +void pa_semaphore_free(pa_semaphore *m); -pa_anotify *pa_anotify_new(pa_mainloop_api*api, pa_anotify_cb_t cb, void *userdata); -void pa_anotify_free(pa_anotify *a); -int pa_anotify_signal(pa_anotify *a, uint8_t event); +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 index 19731b5f..298bf716 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -1,29 +1,29 @@ -/* $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. + 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 <assert.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> @@ -31,37 +31,74 @@ #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 <pulse/gccmacro.h> + #include <pulsecore/core-error.h> #include <pulsecore/log.h> #include <pulsecore/random.h> -#include <pulse/xmalloc.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 +#endif -#define MAX_SHM_SIZE (1024*1024*20) +#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*64)) + +#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_GCC_PACKED { + pa_atomic_t marker; /* 0xbeefcafe */ + pa_atomic_t pid; + uint64_t *_reserverd1; + uint64_t *_reserverd2; + uint64_t *_reserverd3; + uint64_t *_reserverd4; +}; static char *segment_name(char *fn, size_t l, unsigned id) { - snprintf(fn, l, "/pulse-shm-%u", 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) { +int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) { char fn[32]; int fd = -1; - - assert(m); - assert(size > 0); - assert(size < MAX_SHM_SIZE); - assert(mode >= 0600); + + 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; @@ -75,8 +112,8 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { #elif defined(HAVE_POSIX_MEMALIGN) { int r; - - if ((r = posix_memalign(&m->ptr, sysconf(_SC_PAGESIZE), size)) < 0) { + + if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) { pa_log("posix_memalign() failed: %s", pa_cstrerror(r)); goto fail; } @@ -84,11 +121,13 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { #else m->ptr = pa_xmalloc(m->size); #endif - - m->do_unlink = 0; - + + m->do_unlink = FALSE; + } else { #ifdef HAVE_SHM_OPEN + struct shm_marker *marker; + pa_random(&m->id, sizeof(m->id)); segment_name(fn, sizeof(fn), m->id); @@ -96,34 +135,42 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { pa_log("shm_open() failed: %s", pa_cstrerror(errno)); goto fail; } - - if (ftruncate(fd, m->size = size) < 0) { + + 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; } - close(fd); - m->do_unlink = 1; + /* 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 = TRUE; #else - return -1; + return -1; #endif } m->shared = shared; - + return 0; - + fail: #ifdef HAVE_SHM_OPEN if (fd >= 0) { shm_unlink(fn); - close(fd); + pa_close(fd); } #endif @@ -131,77 +178,71 @@ fail: } void pa_shm_free(pa_shm *m) { - assert(m); - assert(m->ptr); - assert(m->size > 0); + pa_assert(m); + pa_assert(m->ptr); + pa_assert(m->size > 0); #ifdef MAP_FAILED - assert(m->ptr != MAP_FAILED); + pa_assert(m->ptr != MAP_FAILED); #endif - if (!m->shared) { + if (!m->shared) { #ifdef MAP_ANONYMOUS - if (munmap(m->ptr, m->size) < 0) - pa_log("munmap() failed: %s", pa_cstrerror(errno)); + 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 { + } else { #ifdef HAVE_SHM_OPEN - if (munmap(m->ptr, m->size) < 0) - pa_log("munmap() failed: %s", pa_cstrerror(errno)); + if (munmap(m->ptr, m->size) < 0) + pa_log("munmap() failed: %s", pa_cstrerror(errno)); + + if (m->do_unlink) { + char fn[32]; - if (m->do_unlink) { - char fn[32]; + segment_name(fn, sizeof(fn), m->id); - segment_name(fn, sizeof(fn), m->id); - - if (shm_unlink(fn) < 0) - pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno)); - } + 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 */ - assert(0); + /* 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; - - assert(m); - assert(m->ptr); - assert(m->size > 0); - assert(offset+size <= m->size); + 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 - assert(m->ptr != 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; - -#ifdef __linux__ -{ - /* On Linux ptr must be page aligned */ - long psz = sysconf(_SC_PAGESIZE); - unsigned o; + o = (uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr); - o = ((unsigned long) ptr) - ((((unsigned long) ptr)/psz) * psz); - if (o > 0) { - ptr = (uint8_t*) ptr + (psz - o); - size -= psz - o; + ps = PA_PAGE_SIZE; + ptr = (uint8_t*) ptr + (ps - o); + size -= ps - o; } -} -#endif - + #ifdef MADV_REMOVE if (madvise(ptr, size, MADV_REMOVE) >= 0) return; @@ -210,10 +251,12 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { #ifdef MADV_FREE if (madvise(ptr, size, MADV_FREE) >= 0) return; -#endif - +#endif + #ifdef MADV_DONTNEED - madvise(ptr, size, 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 } @@ -224,12 +267,13 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { int fd = -1; struct stat st; - assert(m); + pa_assert(m); segment_name(fn, sizeof(fn), m->id = id); if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) { - pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + if (errno != EACCES) + pa_log("shm_open() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -238,13 +282,15 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { goto fail; } - if (st.st_size <= 0 || st.st_size > MAX_SHM_SIZE) { + if (st.st_size <= 0 || + st.st_size > (off_t) (MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker))) || + PA_ALIGN((size_t) st.st_size) != (size_t) 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; @@ -252,14 +298,14 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { m->do_unlink = 0; m->shared = 1; - - close(fd); - + + pa_assert_se(pa_close(fd) == 0); + return 0; - + fail: if (fd >= 0) - close(fd); + pa_close(fd); return -1; } @@ -267,7 +313,73 @@ fail: #else /* HAVE_SHM_OPEN */ int pa_shm_attach_ro(pa_shm *m, unsigned id) { - return -1; + 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 && errno != ENOENT) + 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 index ea72403a..c2adbd07 100644 --- a/src/pulsecore/shm.h +++ b/src/pulsecore/shm.h @@ -1,21 +1,21 @@ #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 @@ -24,19 +24,23 @@ #include <sys/types.h> +#include <pulsecore/macro.h> + typedef struct pa_shm { unsigned id; void *ptr; size_t size; - int do_unlink; - int shared; + pa_bool_t do_unlink:1; + pa_bool_t shared:1; } pa_shm; -int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode); +int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t 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/shmasyncq.c b/src/pulsecore/shmasyncq.c new file mode 100644 index 00000000..eac56adb --- /dev/null +++ b/src/pulsecore/shmasyncq.c @@ -0,0 +1,220 @@ +/*** + 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 "fdsem.h" + +/* 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_shmasyncq { + pa_fdsem *read_fdsem, *write_fdsem; + pa_shmasyncq_data *data; +}; + +static int is_power_of_two(unsigned size) { + return !(size & (size - 1)); +} + +static int reduce(pa_shmasyncq *l, int value) { + return value & (unsigned) (l->n_elements - 1); +} + +static pa_atomic_t* get_cell(pa_shmasyncq *l, unsigned i) { + pa_assert(i < l->data->n_elements); + + return (pa_atomic_t*) ((uint8*t) l->data + PA_ALIGN(sizeof(pa_shmasyncq_data)) + i * (PA_ALIGN(sizeof(pa_atomic_t)) + PA_ALIGN(element_size))) +} + +static void *get_cell_data(pa_atomic_t *a) { + return (uint8_t*) a + PA_ALIGN(sizeof(atomic_t)); +} + +pa_shmasyncq *pa_shmasyncq_new(unsigned n_elements, size_t element_size, void *data, int fd[2]) { + pa_shmasyncq *l; + + pa_assert(n_elements > 0); + pa_assert(is_power_of_two(n_elements)); + pa_assert(element_size > 0); + pa_assert(data); + pa_assert(fd); + + l = pa_xnew(pa_shmasyncq, 1); + + l->data = data; + memset(data, 0, PA_SHMASYNCQ_SIZE(n_elements, element_size)); + + l->data->n_elements = n_elements; + l->data->element_size = element_size; + + if (!(l->read_fdsem = pa_fdsem_new_shm(&d->read_fdsem_data, &fd[0]))) { + pa_xfree(l); + return NULL; + } + + if (!(l->write_fdsem = pa_fdsem_new(&d->write_fdsem_data, &fd[1]))) { + pa_fdsem_free(l->read_fdsem); + pa_xfree(l); + return NULL; + } + + return l; +} + +void pa_shmasyncq_free(pa_shmasyncq *l, pa_free_cb_t free_cb) { + pa_assert(l); + + if (free_cb) { + void *p; + + while ((p = pa_shmasyncq_pop(l, 0))) + free_cb(p); + } + + pa_fdsem_free(l->read_fdsem); + pa_fdsem_free(l->write_fdsem); + pa_xfree(l); +} + +int pa_shmasyncq_push(pa_shmasyncq*l, void *p, int wait) { + int idx; + pa_atomic_ptr_t *cells; + + pa_assert(l); + pa_assert(p); + + cells = PA_SHMASYNCQ_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_shmasyncq_pop(pa_shmasyncq*l, int wait) { + int idx; + void *ret; + pa_atomic_ptr_t *cells; + + pa_assert(l); + + cells = PA_SHMASYNCQ_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_shmasyncq_get_fd(pa_shmasyncq *q) { + pa_assert(q); + + return pa_fdsem_get(q->write_fdsem); +} + +int pa_shmasyncq_before_poll(pa_shmasyncq *l) { + int idx; + pa_atomic_ptr_t *cells; + + pa_assert(l); + + cells = PA_SHMASYNCQ_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_shmasyncq_after_poll(pa_shmasyncq *l) { + pa_assert(l); + + pa_fdsem_after_poll(l->write_fdsem); +} diff --git a/src/pulsecore/shmasyncq.h b/src/pulsecore/shmasyncq.h new file mode 100644 index 00000000..9845c391 --- /dev/null +++ b/src/pulsecore/shmasyncq.h @@ -0,0 +1,60 @@ +#ifndef foopulseshmasyncqhfoo +#define foopulseshmasyncqhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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> +#include <pulsecore/macro.h> + +/* Similar to pa_asyncq, but stores data in a shared memory segment */ + + +struct pa_shmasyncq_data { + unsigned n_elements; + size_t element_size; + unsigned read_idx; + unsigned write_idx; + pa_fdsem_data read_fdsem_data, write_fdsem_data; +}; + +#define PA_SHMASYNCQ_DEFAULT_N_ELEMENTS 128 +#define PA_SHMASYNCQ_SIZE(n_elements, element_size) (PA_ALIGN(sizeof(pa_shmasyncq_data)) + (((n_elements) * (PA_ALIGN(sizeof(pa_atomic_t)) + PA_ALIGN(element_size))))) +#define PA_SHMASYNCQ_DEFAULT_SIZE(element_size) PA_SHMASYNCQ_SIZE(PA_SHMASYNCQ_DEFAULT_N_ELEMENTS, element_size) + +typedef struct pa_shmasyncq pa_shmasyncq; + +pa_shmasyncq *pa_shmasyncq_new(unsigned n_elements, size_t element_size, void *data, int fd[2]); +void pa_shmasyncq_free(pa_shmasyncq* q, pa_free_cb_t free_cb); + +void* pa_shmasyncq_pop_begin(pa_shmasyncq *q, pa_bool_t wait); +void pa_shmasyncq_pop_commit(pa_shmasyncq *q); + +int* pa_shmasyncq_push_begin(pa_shmasyncq *q, pa_bool_t wait); +void pa_shmasyncq_push_commit(pa_shmasyncq *q); + +int pa_shmasyncq_get_fd(pa_shmasyncq *q); +int pa_shmasyncq_before_poll(pa_shmasyncq *a); +void pa_shmasyncq_after_poll(pa_shmasyncq *a); + +#endif diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index c3cd4952..43e7bb21 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,7 +25,6 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> @@ -36,95 +36,154 @@ #include <pulsecore/log.h> #include <pulsecore/play-memblockq.h> #include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> #include "sink-input.h" -#define CONVERT_BUFFER_LENGTH 4096 -#define MOVE_BUFFER_LENGTH (1024*1024) -#define SILENCE_BUFFER_LENGTH (64*1024) +#define MEMBLOCKQ_MAXLENGTH (32*1024*1024) +#define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE) -#define CHECK_VALIDITY_RETURN_NULL(condition) \ -do {\ -if (!(condition)) \ - return NULL; \ -} while (0) +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) { - assert(data); - + pa_assert(data); + memset(data, 0, sizeof(*data)); data->resample_method = PA_RESAMPLER_INVALID; + data->proplist = pa_proplist_new(); + return data; } +void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map) { - assert(data); + 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) { - assert(data); + 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) { - assert(data); +void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) { + pa_assert(data); - if ((data->sample_spec_is_set = !!spec)) - data->sample_spec = *spec; + data->muted_is_set = TRUE; + data->muted = !!mute; +} + +void pa_sink_input_new_data_done(pa_sink_input_new_data *data) { + pa_assert(data); + + pa_proplist_free(data->proplist); +} + +/* Called from main context */ +static void reset_callbacks(pa_sink_input *i) { + pa_assert(i); + + i->pop = NULL; + i->process_rewind = NULL; + i->update_max_rewind = NULL; + i->update_max_request = NULL; + i->update_sink_requested_latency = NULL; + i->update_sink_latency_range = NULL; + i->attach = NULL; + i->detach = NULL; + i->suspend = NULL; + i->moved = NULL; + i->kill = NULL; + i->get_latency = NULL; + i->state_change = NULL; } +/* Called from main context */ 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; - int r; - char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; - assert(core); - assert(data); + pa_assert(core); + pa_assert(data); - if (!(flags & PA_SINK_INPUT_NO_HOOKS)) - if (pa_hook_fire(&core->hook_sink_input_new, data) < 0) - return NULL; + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data) < 0) + return NULL; - CHECK_VALIDITY_RETURN_NULL(!data->driver || pa_utf8_valid(data->driver)); - CHECK_VALIDITY_RETURN_NULL(!data->name || pa_utf8_valid(data->name)); + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); if (!data->sink) - data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK, 1); - - CHECK_VALIDITY_RETURN_NULL(data->sink); - CHECK_VALIDITY_RETURN_NULL(data->sink->state == PA_SINK_RUNNING); + data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK, TRUE); + + 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; - - CHECK_VALIDITY_RETURN_NULL(pa_sample_spec_valid(&data->sample_spec)); - - if (!data->channel_map_is_set) - pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); - - CHECK_VALIDITY_RETURN_NULL(pa_channel_map_valid(&data->channel_map)); - CHECK_VALIDITY_RETURN_NULL(data->channel_map.channels == data->sample_spec.channels); - + + 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); - CHECK_VALIDITY_RETURN_NULL(pa_cvolume_valid(&data->volume)); - CHECK_VALIDITY_RETURN_NULL(data->volume.channels == 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 (!data->muted_is_set) + data->muted = FALSE; + + 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->resample_method == PA_RESAMPLER_INVALID) data->resample_method = core->resample_method; - CHECK_VALIDITY_RETURN_NULL(data->resample_method < PA_RESAMPLER_MAX); + 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."); @@ -133,541 +192,1011 @@ pa_sink_input* pa_sink_input_new( 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)) - + !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { + if (!(resampler = pa_resampler_new( - core->mempool, + core->mempool, &data->sample_spec, &data->channel_map, &data->sink->sample_spec, &data->sink->channel_map, - data->resample_method))) { + 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; } - i = pa_xnew(pa_sink_input, 1); - i->ref = 1; - i->state = PA_SINK_INPUT_DRAINED; + 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->proplist = pa_proplist_copy(data->proplist); 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->peek = NULL; - i->drop = NULL; - i->kill = NULL; - i->get_latency = NULL; - i->underrun = NULL; + i->muted = data->muted; + + if (data->sync_base) { + i->sync_next = data->sync_base->sync_next; + i->sync_prev = data->sync_base; + + if (data->sync_base->sync_next) + data->sync_base->sync_next->sync_prev = i; + data->sync_base->sync_next = i; + } else + i->sync_next = i->sync_prev = NULL; + + i->direct_outputs = pa_idxset_new(NULL, NULL); + + reset_callbacks(i); i->userdata = NULL; - - i->move_silence = 0; - pa_memchunk_reset(&i->resampled_chunk); - i->resampler = resampler; - i->resample_method = data->resample_method; - i->silence_memblock = NULL; - - r = pa_idxset_put(core->sink_inputs, i, &i->index); - assert(r == 0); - r = pa_idxset_put(i->sink->inputs, i, NULL); - assert(r == 0); - - pa_log_info("created %u \"%s\" on %s with sample spec %s", + i->thread_info.state = i->state; + i->thread_info.attached = FALSE; + pa_atomic_store(&i->thread_info.drained, 1); + i->thread_info.sample_spec = i->sample_spec; + i->thread_info.resampler = resampler; + i->thread_info.volume = i->volume; + i->thread_info.muted = i->muted; + i->thread_info.requested_sink_latency = (pa_usec_t) -1; + i->thread_info.rewrite_nbytes = 0; + i->thread_info.rewrite_flush = FALSE; + i->thread_info.underrun_for = (uint64_t) -1; + i->thread_info.playing_for = 0; + i->thread_info.direct_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + i->thread_info.render_memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + pa_frame_size(&i->sink->sample_spec), + 0, + 1, + 0, + &i->sink->silence); + + pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0); + pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0); + + pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s", i->index, - i->name, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), i->sink->name, - pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec)); - - pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index); + 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! */ - /* We do not call pa_sink_notify() here, because the virtual - * functions have not yet been initialized */ - - return i; + return i; +} + +/* Called from main context */ +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); } -void pa_sink_input_disconnect(pa_sink_input *i) { - assert(i); - assert(i->state != PA_SINK_INPUT_DISCONNECTED); - assert(i->sink); - assert(i->sink->core); +/* Called from main context */ +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; + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); + + 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); + + for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev) + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync); + + for (ssync = i->sync_next; ssync; ssync = ssync->sync_next) + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync); + } + + return 0; +} + +/* Called from main context */ +void pa_sink_input_unlink(pa_sink_input *i) { + pa_bool_t linked; + pa_source_output *o, *p = NULL; + 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_IS_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); - pa_idxset_remove_by_data(i->sink->inputs, i, NULL); + if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL)) + pa_sink_input_unref(i); - pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index); - i->sink = NULL; + while ((o = pa_idxset_first(i->direct_outputs, NULL))) { + pa_assert(o != p); + pa_source_output_kill(o); + p = o; + } - i->peek = NULL; - i->drop = NULL; - i->kill = NULL; - i->get_latency = NULL; - i->underrun = NULL; + update_n_corked(i, PA_SINK_INPUT_UNLINKED); + i->state = PA_SINK_INPUT_UNLINKED; + + if (linked) + if (i->sink->asyncmsgq) + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0); + + reset_callbacks(i); - i->state = PA_SINK_INPUT_DISCONNECTED; + 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_sink_input* i) { - assert(i); +/* Called from main context */ +static void sink_input_free(pa_object *o) { + pa_sink_input* i = PA_SINK_INPUT(o); - if (i->state != PA_SINK_INPUT_DISCONNECTED) - pa_sink_input_disconnect(i); + pa_assert(i); + pa_assert(pa_sink_input_refcnt(i) == 0); - pa_log_info("freed %u \"%s\"", i->index, i->name); - - if (i->resampled_chunk.memblock) - pa_memblock_unref(i->resampled_chunk.memblock); - - if (i->resampler) - pa_resampler_free(i->resampler); + if (PA_SINK_INPUT_IS_LINKED(i->state)) + pa_sink_input_unlink(i); + + pa_log_info("Freeing input %u \"%s\"", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME))); + + pa_assert(!i->thread_info.attached); + + if (i->thread_info.render_memblockq) + pa_memblockq_free(i->thread_info.render_memblockq); + + if (i->thread_info.resampler) + pa_resampler_free(i->thread_info.resampler); + + if (i->proplist) + pa_proplist_free(i->proplist); + + if (i->direct_outputs) + pa_idxset_free(i->direct_outputs, NULL, NULL); + + if (i->thread_info.direct_outputs) + pa_hashmap_free(i->thread_info.direct_outputs, NULL, NULL); - if (i->silence_memblock) - pa_memblock_unref(i->silence_memblock); - - pa_xfree(i->name); pa_xfree(i->driver); pa_xfree(i); } -void pa_sink_input_unref(pa_sink_input *i) { - assert(i); - assert(i->ref >= 1); +/* Called from main context */ +void pa_sink_input_put(pa_sink_input *i) { + pa_sink_input_state_t state; + pa_sink_input_assert_ref(i); - if (!(--i->ref)) - sink_input_free(i); -} + pa_assert(i->state == PA_SINK_INPUT_INIT); -pa_sink_input* pa_sink_input_ref(pa_sink_input *i) { - assert(i); - assert(i->ref >= 1); - - i->ref++; - return i; + /* The following fields must be initialized properly */ + pa_assert(i->pop); + pa_assert(i->process_rewind); + pa_assert(i->kill); + + i->thread_info.volume = i->volume; + i->thread_info.muted = i->muted; + + state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING; + + update_n_corked(i, state); + i->state = state; + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL) == 0); + + pa_subscription_post(i->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); } +/* Called from main context */ void pa_sink_input_kill(pa_sink_input*i) { - assert(i); - assert(i->ref >= 1); + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - if (i->kill) - i->kill(i); + i->kill(i); } -pa_usec_t pa_sink_input_get_latency(pa_sink_input *i) { - pa_usec_t r = 0; - - assert(i); - assert(i->ref >= 1); - - if (i->get_latency) - r += i->get_latency(i); +/* Called from main context */ +pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) { + pa_usec_t r[2] = { 0, 0 }; - if (i->resampled_chunk.memblock) - r += pa_bytes_to_usec(i->resampled_chunk.length, &i->sink->sample_spec); + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - if (i->move_silence) - r += pa_bytes_to_usec(i->move_silence, &i->sink->sample_spec); + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0); - return r; + if (i->get_latency) + r[0] += i->get_latency(i); + + if (sink_latency) + *sink_latency = r[1]; + + return r[0]; } -int pa_sink_input_peek(pa_sink_input *i, pa_memchunk *chunk, pa_cvolume *volume) { - int ret = -1; - int do_volume_adj_here; - int volume_is_norm; - - assert(i); - assert(i->ref >= 1); - assert(chunk); - assert(volume); +/* Called from thread context */ +int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) { + pa_bool_t do_volume_adj_here; + pa_bool_t volume_is_norm; + size_t block_size_max_sink, block_size_max_sink_input; + size_t ilength; - pa_sink_input_ref(i); + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec)); + pa_assert(chunk); + pa_assert(volume); - if (!i->peek || !i->drop || i->state == PA_SINK_INPUT_CORKED) - goto finish; +/* pa_log_debug("peek"); */ - assert(i->state == PA_SINK_INPUT_RUNNING || i->state == PA_SINK_INPUT_DRAINED); + if (!i->pop) + return -1; - if (i->move_silence > 0) { - size_t l; + pa_assert(i->thread_info.state == PA_SINK_INPUT_RUNNING || + i->thread_info.state == PA_SINK_INPUT_CORKED || + i->thread_info.state == PA_SINK_INPUT_DRAINED); - /* We have just been moved and shall play some silence for a - * while until the old sink has drained its playback buffer */ - - if (!i->silence_memblock) - i->silence_memblock = pa_silence_memblock_new(i->sink->core->mempool, &i->sink->sample_spec, SILENCE_BUFFER_LENGTH); + /* If there's still some rewrite request the handle, but the sink + didn't do this for us, we do it here. However, since the sink + apparently doesn't support rewinding, we pass 0 here. This still + allows rewinding through the render buffer. */ + pa_sink_input_process_rewind(i, 0); - chunk->memblock = pa_memblock_ref(i->silence_memblock); - chunk->index = 0; - l = pa_memblock_get_length(chunk->memblock); - chunk->length = i->move_silence < l ? i->move_silence : l; + block_size_max_sink_input = i->thread_info.resampler ? + pa_resampler_max_block_size(i->thread_info.resampler) : + pa_frame_align(pa_mempool_block_size_max(i->sink->core->mempool), &i->sample_spec); - ret = 0; - do_volume_adj_here = 1; - goto finish; - } - - if (!i->resampler) { - do_volume_adj_here = 0; - ret = i->peek(i, chunk); - goto finish; - } + block_size_max_sink = pa_frame_align(pa_mempool_block_size_max(i->sink->core->mempool), &i->sink->sample_spec); + + /* Default buffer size */ + if (slength <= 0) + slength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sink->sample_spec); + + if (slength > block_size_max_sink) + slength = block_size_max_sink; + + if (i->thread_info.resampler) { + ilength = pa_resampler_request(i->thread_info.resampler, slength); + + if (ilength <= 0) + ilength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sample_spec); + } else + ilength = slength; + + if (ilength > block_size_max_sink_input) + ilength = block_size_max_sink_input; + + /* If the channel maps of the sink and this stream differ, we need + * to adjust the volume *before* we resample. Otherwise we can do + * it after and leave it for the sink code */ do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map); - volume_is_norm = pa_cvolume_is_norm(&i->volume); - - while (!i->resampled_chunk.memblock) { + volume_is_norm = pa_cvolume_is_norm(&i->thread_info.volume) && !i->thread_info.muted; + + while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) { pa_memchunk tchunk; - size_t l; - - if ((ret = i->peek(i, &tchunk)) < 0) - goto finish; - assert(tchunk.length); - - l = pa_resampler_request(i->resampler, CONVERT_BUFFER_LENGTH); + /* There's nothing in our render queue. We need to fill it up + * with data from the implementor. */ - if (l > tchunk.length) - l = tchunk.length; + if (i->thread_info.state == PA_SINK_INPUT_CORKED || + i->pop(i, ilength, &tchunk) < 0) { - i->drop(i, &tchunk, l); - tchunk.length = l; + /* OK, we're corked or the implementor didn't give us any + * data, so let's just hand out silence */ + pa_atomic_store(&i->thread_info.drained, 1); - /* It might be necessary to adjust the volume here */ - if (do_volume_adj_here && !volume_is_norm) { - pa_memchunk_make_writable(&tchunk, 0); - pa_volume_memchunk(&tchunk, &i->sample_spec, &i->volume); + pa_memblockq_seek(i->thread_info.render_memblockq, slength, PA_SEEK_RELATIVE); + i->thread_info.playing_for = 0; + if (i->thread_info.underrun_for != (uint64_t) -1) + i->thread_info.underrun_for += ilength; + break; + } + + pa_atomic_store(&i->thread_info.drained, 0); + + pa_assert(tchunk.length > 0); + pa_assert(tchunk.memblock); + + i->thread_info.underrun_for = 0; + i->thread_info.playing_for += tchunk.length; + + while (tchunk.length > 0) { + pa_memchunk wchunk; + + wchunk = tchunk; + pa_memblock_ref(wchunk.memblock); + + if (wchunk.length > block_size_max_sink_input) + wchunk.length = block_size_max_sink_input; + + /* It might be necessary to adjust the volume here */ + if (do_volume_adj_here && !volume_is_norm) { + pa_memchunk_make_writable(&wchunk, 0); + + if (i->thread_info.muted) + pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec); + else + pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.volume); + } + + if (!i->thread_info.resampler) + pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk); + else { + pa_memchunk rchunk; + pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk); + +/* pa_log_debug("pushing %lu", (unsigned long) rchunk.length); */ + + if (rchunk.memblock) { + pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk); + pa_memblock_unref(rchunk.memblock); + } + } + + pa_memblock_unref(wchunk.memblock); + + tchunk.index += wchunk.length; + tchunk.length -= wchunk.length; } - pa_resampler_run(i->resampler, &tchunk, &i->resampled_chunk); pa_memblock_unref(tchunk.memblock); } - assert(i->resampled_chunk.memblock); - assert(i->resampled_chunk.length); - - *chunk = i->resampled_chunk; - pa_memblock_ref(i->resampled_chunk.memblock); + pa_assert_se(pa_memblockq_peek(i->thread_info.render_memblockq, chunk) >= 0); - ret = 0; + pa_assert(chunk->length > 0); + pa_assert(chunk->memblock); -finish: +/* pa_log_debug("peeking %lu", (unsigned long) chunk->length); */ - if (ret < 0 && i->state == PA_SINK_INPUT_RUNNING && i->underrun) - i->underrun(i); + if (chunk->length > block_size_max_sink) + chunk->length = block_size_max_sink; - if (ret >= 0) - i->state = PA_SINK_INPUT_RUNNING; - else if (ret < 0 && i->state == PA_SINK_INPUT_RUNNING) - i->state = PA_SINK_INPUT_DRAINED; + /* Let's see if we had to apply the volume adjustment ourselves, + * or if this can be done by the sink for us */ - 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; - 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 - /* We've both the same channel map, so let's have the sink do the adjustment for us*/ - *volume = i->volume; - } - - pa_sink_input_unref(i); - - return ret; + return 0; } -void pa_sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) { - assert(i); - assert(i->ref >= 1); - assert(length > 0); +/* Called from thread context */ +void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { + pa_sink_input_assert_ref(i); - if (i->move_silence > 0) { + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + pa_assert(nbytes > 0); - if (chunk) { - size_t l; +/* pa_log_debug("dropping %lu", (unsigned long) nbytes); */ - l = pa_memblock_get_length(i->silence_memblock); - - if (chunk->memblock != i->silence_memblock || - chunk->index != 0 || - (chunk->memblock && (chunk->length != (l < i->move_silence ? l : i->move_silence)))) - return; - - } + /* If there's still some rewrite request the handle, but the sink + didn't do this for us, we do it here. However, since the sink + apparently doesn't support rewinding, we pass 0 here. This still + allows rewinding through the render buffer. */ + pa_sink_input_process_rewind(i, 0); - assert(i->move_silence >= length); - - i->move_silence -= length; + pa_memblockq_drop(i->thread_info.render_memblockq, nbytes); +} - if (i->move_silence <= 0) { - assert(i->silence_memblock); - pa_memblock_unref(i->silence_memblock); - i->silence_memblock = NULL; - } +/* Called from thread context */ +void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { + size_t lbq; + pa_sink_input_assert_ref(i); - return; + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + +/* pa_log_debug("rewind(%lu, %lu)", (unsigned long) nbytes, (unsigned long) i->thread_info.rewrite_nbytes); */ + + lbq = pa_memblockq_get_length(i->thread_info.render_memblockq); + + if (nbytes > 0) { + pa_log_debug("Have to rewind %lu bytes on render memblockq.", (unsigned long) nbytes); + pa_memblockq_rewind(i->thread_info.render_memblockq, nbytes); } - if (!i->resampler) { - if (i->drop) - i->drop(i, chunk, length); - return; + if (i->thread_info.rewrite_nbytes == (size_t) -1) { + + /* We were asked to drop all buffered data, and rerequest new + * data from implementor the next time push() is called */ + + pa_memblockq_flush(i->thread_info.render_memblockq); + + } else if (i->thread_info.rewrite_nbytes > 0) { + size_t max_rewrite, amount; + + /* Calculate how much make sense to rewrite at most */ + max_rewrite = nbytes + lbq; + + /* Transform into local domain */ + if (i->thread_info.resampler) + max_rewrite = pa_resampler_request(i->thread_info.resampler, max_rewrite); + + /* Calculate how much of the rewinded data should actually be rewritten */ + amount = PA_MIN(i->thread_info.rewrite_nbytes, max_rewrite); + + if (amount > 0) { + pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) amount); + + /* Tell the implementor */ + if (i->process_rewind) + i->process_rewind(i, amount); + + /* Convert back to to sink domain */ + if (i->thread_info.resampler) + amount = pa_resampler_result(i->thread_info.resampler, amount); + + if (amount > 0) + /* Ok, now update the write pointer */ + pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE); + + if (i->thread_info.rewrite_flush) + pa_memblockq_silence(i->thread_info.render_memblockq); + + /* And reset the resampler */ + if (i->thread_info.resampler) + pa_resampler_reset(i->thread_info.resampler); + } } - - assert(i->resampled_chunk.memblock); - assert(i->resampled_chunk.length >= length); - i->resampled_chunk.index += length; - i->resampled_chunk.length -= length; + i->thread_info.rewrite_nbytes = 0; + i->thread_info.rewrite_flush = FALSE; +} + +/* Called from thread context */ +void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + + pa_memblockq_set_maxrewind(i->thread_info.render_memblockq, nbytes); - if (i->resampled_chunk.length <= 0) { - pa_memblock_unref(i->resampled_chunk.memblock); - i->resampled_chunk.memblock = NULL; - i->resampled_chunk.index = i->resampled_chunk.length = 0; + if (i->update_max_rewind) + i->update_max_rewind(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes); +} + +/* Called from thread context */ +void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + + if (i->update_max_request) + i->update_max_request(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes); +} + +/* Called from thread context */ +static pa_usec_t fixup_latency(pa_sink *s, pa_usec_t usec) { + pa_sink_assert_ref(s); + + if (usec == (pa_usec_t) -1) + return usec; + + if (s->thread_info.max_latency > 0 && usec > s->thread_info.max_latency) + usec = s->thread_info.max_latency; + + if (s->thread_info.min_latency > 0 && usec < s->thread_info.min_latency) + usec = s->thread_info.min_latency; + + return usec; +} + +/* Called from thread context */ +pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) { + pa_sink_input_assert_ref(i); + + usec = fixup_latency(i->sink, usec); + i->thread_info.requested_sink_latency = usec; + pa_sink_invalidate_requested_latency(i->sink); + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) { + pa_sink_input_assert_ref(i); + + if (PA_SINK_INPUT_IS_LINKED(i->state)) + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + else { + /* If this sink input is not realized yet, we have to touch + * the thread info data directly */ + + usec = fixup_latency(i->sink, usec); + i->thread_info.requested_sink_latency = usec; + i->sink->thread_info.requested_latency_valid = FALSE; } + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { + pa_usec_t usec = 0; + + pa_sink_input_assert_ref(i); + + if (PA_SINK_INPUT_IS_LINKED(i->state)) + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + else + /* If this sink input is not realized yet, we have to touch + * the thread info data directly */ + usec = i->thread_info.requested_sink_latency; + + return usec; } +/* Called from main context */ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume) { - assert(i); - assert(i->ref >= 1); - assert(i->sink); - assert(i->sink->core); + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(volume); if (pa_cvolume_equal(&i->volume, volume)) return; - + i->volume = *volume; + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME, &i->volume, 0, NULL) == 0); 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) { - assert(i); - assert(i->ref >= 1); +/* Called from main context */ +const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); return &i->volume; } -void pa_sink_input_cork(pa_sink_input *i, int b) { - int n; - - assert(i); - assert(i->ref >= 1); +/* Called from main context */ +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_IS_LINKED(i->state)); + + if (!i->muted == !mute) + return; - assert(i->state != PA_SINK_INPUT_DISCONNECTED); + i->muted = mute; - n = i->state == PA_SINK_INPUT_CORKED && !b; + 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); +} - if (b) - i->state = PA_SINK_INPUT_CORKED; - else if (i->state == PA_SINK_INPUT_CORKED) - i->state = PA_SINK_INPUT_DRAINED; +/* Called from main context */ +pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - if (n) - pa_sink_notify(i->sink); + return i->muted; } -void pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { - assert(i); - assert(i->resampler); - assert(i->ref >= 1); +/* Called from main context */ +void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING); +} + +/* Called from main context */ +int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_return_val_if_fail(i->thread_info.resampler, -1); if (i->sample_spec.rate == rate) - return; + return 0; i->sample_spec.rate = rate; - pa_resampler_set_input_rate(i->resampler, 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; } +/* Called from main context */ void pa_sink_input_set_name(pa_sink_input *i, const char *name) { - assert(i); - assert(i->ref >= 1); + const char *old; + pa_sink_input_assert_ref(i); - if (!i->name && !name) + if (!name && !pa_proplist_contains(i->proplist, PA_PROP_MEDIA_NAME)) return; - if (i->name && name && !strcmp(i->name, name)) + old = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME); + + if (old && name && !strcmp(old, name)) return; - - pa_xfree(i->name); - i->name = pa_xstrdup(name); - pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + if (name) + pa_proplist_sets(i->proplist, PA_PROP_MEDIA_NAME, name); + else + pa_proplist_unset(i->proplist, PA_PROP_MEDIA_NAME); + + if (PA_SINK_INPUT_IS_LINKED(i->state)) { + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } } +/* Called from main context */ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) { - assert(i); - assert(i->ref >= 1); - - if (!i->resampler) - return i->resample_method; + pa_sink_input_assert_ref(i); - return pa_resampler_get_method(i->resampler); + return i->resample_method; } -int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) { - pa_resampler *new_resampler = NULL; - pa_memblockq *buffer = NULL; +/* Called from main context */ +int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest) { + pa_resampler *new_resampler; pa_sink *origin; - - assert(i); - assert(dest); + pa_sink_input_move_hook_data hook_data; + pa_source_output *o, *p = NULL; + + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_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->resampler && + /* Kill directly connected outputs */ + while ((o = pa_idxset_first(i->direct_outputs, NULL))) { + pa_assert(o != p); + pa_source_output_kill(o); + p = o; + } + + 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->resampler; - + 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)) { + !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->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); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); + + 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++; } - if (!immediately) { - pa_usec_t old_latency, new_latency; - pa_usec_t silence_usec = 0; - - buffer = pa_memblockq_new(0, MOVE_BUFFER_LENGTH, 0, pa_frame_size(&origin->sample_spec), 0, 0, NULL); - - /* Let's do a little bit of Voodoo for compensating latency - * differences */ - - 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 { - size_t l; - int volume_is_norm; - - /* 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.*/ - - l = pa_usec_to_bytes(new_latency - old_latency, &origin->sample_spec); - - volume_is_norm = pa_cvolume_is_norm(&i->volume); - - while (l > 0) { - pa_memchunk chunk; - pa_cvolume volume; - size_t n; - - if (pa_sink_input_peek(i, &chunk, &volume) < 0) - break; - - n = chunk.length > l ? l : chunk.length; - pa_sink_input_drop(i, &chunk, n); - chunk.length = n; - - if (!volume_is_norm) { - pa_memchunk_make_writable(&chunk, 0); - pa_volume_memchunk(&chunk, &origin->sample_spec, &volume); - } + /* Replace resampler and render queue */ + if (new_resampler != i->thread_info.resampler) { - if (pa_memblockq_push(buffer, &chunk) < 0) { - pa_memblock_unref(chunk.memblock); - break; - } + if (i->thread_info.resampler) + pa_resampler_free(i->thread_info.resampler); + i->thread_info.resampler = new_resampler; - pa_memblock_unref(chunk.memblock); - l -= n; - } + pa_memblockq_free(i->thread_info.render_memblockq); + + i->thread_info.render_memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + pa_frame_size(&i->sink->sample_spec), + 0, + 1, + 0, + &i->sink->silence); + } + + pa_sink_update_status(origin); + pa_sink_update_status(dest); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); + + 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 IO thread context */ +void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) { + pa_sink_input_assert_ref(i); + + if (state == i->thread_info.state) + return; + + if ((state == PA_SINK_INPUT_DRAINED || state == 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); + + if (state == PA_SINK_INPUT_CORKED && i->thread_info.state != PA_SINK_INPUT_CORKED) { + + /* This will tell the implementing sink input driver to rewind + * so that the unplayed already mixed data is not lost */ + pa_sink_input_request_rewind(i, 0, TRUE, TRUE); + + } else if (i->thread_info.state == PA_SINK_INPUT_CORKED && state != PA_SINK_INPUT_CORKED) { + + /* OK, we're being uncorked. Make sure we're not rewound when + * the hw buffer is remixed and request a remix. */ + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); + } + + if (i->state_change) + i->state_change(i, state); + + i->thread_info.state = state; +} + +/* Called from thread context, except when it is not. */ +int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + pa_sink_input_assert_ref(i); + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_SET_VOLUME: + i->thread_info.volume = *((pa_cvolume*) userdata); + pa_sink_input_request_rewind(i, 0, TRUE, FALSE); + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_MUTE: + i->thread_info.muted = PA_PTR_TO_UINT(userdata); + pa_sink_input_request_rewind(i, 0, TRUE, FALSE); + return 0; + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + pa_usec_t sink_usec = 0; + + r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); + + if (i->sink->parent.process_msg(PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_usec, 0, NULL) >= 0) + r[1] += sink_usec; + + return 0; } - if (i->resampled_chunk.memblock) { + 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)); - /* There is still some data left in the already resampled - * memory block. Hence, let's output it on the old sink - * and sleep so long on the new sink */ + return 0; - pa_memblockq_push(buffer, &i->resampled_chunk); - silence_usec += pa_bytes_to_usec(i->resampled_chunk.length, &origin->sample_spec); + case PA_SINK_INPUT_MESSAGE_SET_STATE: { + pa_sink_input *ssync; + + pa_sink_input_set_state_within_thread(i, PA_PTR_TO_UINT(userdata)); + + for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev) + pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata)); + + for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next) + pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata)); + + return 0; } - /* Calculate the new sleeping time */ - i->move_silence = pa_usec_to_bytes( - pa_bytes_to_usec(i->move_silence, &i->sample_spec) + - silence_usec, - &i->sample_spec); - } + case PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY: { + pa_usec_t *usec = userdata; - /* Okey, let's move it */ - pa_idxset_remove_by_data(origin->inputs, i, NULL); - pa_idxset_put(dest->inputs, i, NULL); - i->sink = dest; + *usec = pa_sink_input_set_requested_latency_within_thread(i, *usec); + return 0; + } + + case PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY: { + pa_usec_t *r = userdata; - /* Replace resampler */ - if (new_resampler != i->resampler) { - if (i->resampler) - pa_resampler_free(i->resampler); - i->resampler = new_resampler; - - /* if the resampler changed, the silence memblock is - * probably invalid now, too */ - if (i->silence_memblock) { - pa_memblock_unref(i->silence_memblock); - i->silence_memblock = NULL; + *r = i->thread_info.requested_sink_latency; + return 0; } } - /* Dump already resampled data */ - if (i->resampled_chunk.memblock) { - pa_memblock_unref(i->resampled_chunk.memblock); - i->resampled_chunk.memblock = NULL; - i->resampled_chunk.index = i->resampled_chunk.length = 0; + return -1; +} + +/* Called from main thread */ +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; +} + +/* Called from IO context */ +pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) + return pa_memblockq_is_empty(i->thread_info.render_memblockq); + + return TRUE; +} + +/* Called from IO context */ +void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes /* in our sample spec */, pa_bool_t rewrite, pa_bool_t flush) { + size_t lbq; + + /* If 'rewrite' is TRUE the sink is rewound as far as requested + * and possible and the exact value of this is passed back the + * implementor via process_rewind(). If 'flush' is also TRUE all + * already rendered data is also dropped. + * + * If 'rewrite' is FALSE the sink is rewound as far as requested + * and possible and the already rendered data is dropped so that + * in the next iteration we read new data from the + * implementor. This implies 'flush' is TRUE. */ + + pa_sink_input_assert_ref(i); + pa_assert(i->thread_info.rewrite_nbytes == 0); + + /* We don't take rewind requests while we are corked */ + if (i->thread_info.state == PA_SINK_INPUT_CORKED) + return; + + pa_assert(rewrite || flush); + + /* Calculate how much we can rewind locally without having to + * touch the sink */ + if (rewrite) + lbq = pa_memblockq_get_length(i->thread_info.render_memblockq); + else + lbq = 0; + + /* Check if rewinding for the maximum is requested, and if so, fix up */ + if (nbytes <= 0) { + + /* Calculate maximum number of bytes that could be rewound in theory */ + nbytes = i->sink->thread_info.max_rewind + lbq; + + /* Transform from sink domain */ + if (i->thread_info.resampler) + nbytes = pa_resampler_request(i->thread_info.resampler, nbytes); } - - /* Notify everyone */ - pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); - pa_sink_notify(i->sink); - /* Ok, no let's feed the precomputed buffer to the old sink */ - if (buffer) - pa_play_memblockq(origin, "Ghost Stream", &origin->sample_spec, &origin->channel_map, buffer, NULL); + if (rewrite) { + /* Make sure to not overwrite over underruns */ + if (nbytes > i->thread_info.playing_for) + nbytes = (size_t) i->thread_info.playing_for; - return 0; + i->thread_info.rewrite_nbytes = nbytes; + } else + i->thread_info.rewrite_nbytes = (size_t) -1; + + i->thread_info.rewrite_flush = flush && i->thread_info.rewrite_nbytes != 0; + + /* Transform to sink domain */ + if (i->thread_info.resampler) + nbytes = pa_resampler_result(i->thread_info.resampler, nbytes); + + if (nbytes > lbq) + pa_sink_request_rewind(i->sink, nbytes - lbq); +} + +/* Called from main context */ +pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) { + pa_sink_input_assert_ref(i); + pa_assert(ret); + + pa_silence_memchunk_get( + &i->sink->core->silence_cache, + i->sink->core->mempool, + ret, + &i->sample_spec, + i->thread_info.resampler ? pa_resampler_max_block_size(i->thread_info.resampler) : 0); + + return ret; } diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 2943dfae..c07a7404 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -1,21 +1,22 @@ -#ifndef foosinkinputhfoo -#define foosinkinputhfoo - -/* $Id$ */ +#ifndef foopulsesinkinputhfoo +#define foopulsesinkinputhfoo /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -36,106 +37,271 @@ typedef struct pa_sink_input pa_sink_input; #include <pulsecore/core.h> typedef enum pa_sink_input_state { - PA_SINK_INPUT_RUNNING, /*< The stream is alive and kicking */ + 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_DISCONNECTED /*< The stream is dead */ + PA_SINK_INPUT_UNLINKED /*< The stream is dead */ } pa_sink_input_state_t; +static inline pa_bool_t PA_SINK_INPUT_IS_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_NO_HOOKS = 2 + 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 { - int ref; + 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 */ + + char *driver; /* may be NULL */ + pa_proplist *proplist; + + pa_module *module; /* may be NULL */ + pa_client *client; /* may be NULL */ pa_sink *sink; - + + /* A sink input may be connected to multiple source outputs + * directly, so that they don't get mixed data of the entire + * source. */ + pa_idxset *direct_outputs; + pa_sample_spec sample_spec; pa_channel_map channel_map; + + pa_sink_input *sync_prev, *sync_next; + pa_cvolume volume; + pa_bool_t muted; + + pa_resample_method_t resample_method; - /* 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; - - int (*peek) (pa_sink_input *i, pa_memchunk *chunk); - void (*drop) (pa_sink_input *i, const pa_memchunk *chunk, size_t length); - void (*kill) (pa_sink_input *i); /* may be NULL */ + /* Returns the chunk of audio data and drops it from the + * queue. Returns -1 on failure. Called from IO thread context. If + * data needs to be generated from scratch then please in the + * specified length request_nbytes. This is an optimization + * only. If less data is available, it's fine to return a smaller + * block. If more data is already ready, it is better to return + * the full block. */ + int (*pop) (pa_sink_input *i, size_t request_nbytes, pa_memchunk *chunk); /* may NOT be NULL */ + + /* Rewind the queue by the specified number of bytes. Called just + * before peek() if it is called at all. Only called if the sink + * input driver ever plans to call + * pa_sink_input_request_rewind(). Called from IO context. */ + void (*process_rewind) (pa_sink_input *i, size_t nbytes); /* may NOT be NULL */ + + /* Called whenever the maximum rewindable size of the sink + * changes. Called from IO context. */ + void (*update_max_rewind) (pa_sink_input *i, size_t nbytes); /* may be NULL */ + + /* Called whenever the maxiumum request size of the sink + * changes. Called from IO context. */ + void (*update_max_request) (pa_sink_input *i, size_t nbytes); /* may be NULL */ + + /* Called whenever the configured latency of the sink + * changes. Called from IO context. */ + void (*update_sink_requested_latency) (pa_sink_input *i); /* may be NULL */ + + /* Called whenver the latency range of the sink changes. Called + * from IO context. */ + void (*update_sink_latency_range) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called when the input is first + * connected to a sink or when the rtpoll/asyncmsgq fields + * change. You usually don't need to implement this function + * unless you rewrite a sink that is piggy-backed onto + * another. Called from IO thread context */ + void (*attach) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called when the output is + * disconnected from its sink. Called from IO thread context */ + void (*detach) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL called whenever the 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 NOT be NULL */ + + /* Return the current latency (i.e. length of bufferd audio) of + this stream. Called from main context. This is added to what the + PA_SINK_INPUT_MESSAGE_GET_LATENCY message sent to the IO thread + returns */ pa_usec_t (*get_latency) (pa_sink_input *i); /* may be NULL */ - void (*underrun) (pa_sink_input *i); /* may be NULL */ - void *userdata; + /* If non_NULL this function is called from thread context if the + * state changes. The old state is found in thread_info.state. */ + void (*state_change) (pa_sink_input *i, pa_sink_input_state_t state); /* may be NULL */ - pa_memchunk resampled_chunk; - pa_resampler *resampler; /* may be NULL */ + struct { + pa_sink_input_state_t state; + pa_atomic_t drained; - pa_resample_method_t resample_method; + pa_bool_t attached; /* True only between ->attach() and ->detach() calls */ + + pa_sample_spec sample_spec; + + pa_resampler *resampler; /* may be NULL */ + + /* We maintain a history of resampled audio data here. */ + pa_memblockq *render_memblockq; + + size_t rewrite_nbytes; + pa_bool_t rewrite_flush; + uint64_t underrun_for, playing_for; - pa_memblock *silence_memblock; /* may be NULL */ + pa_sink_input *sync_prev, *sync_next; + + pa_cvolume volume; + pa_bool_t muted; + + /* The requested latency for the sink */ + pa_usec_t requested_sink_latency; + + pa_hashmap *direct_outputs; + } 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_SET_REQUESTED_LATENCY, + PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, + PA_SINK_INPUT_MESSAGE_MAX }; typedef struct pa_sink_input_new_data { - const char *name, *driver; + pa_proplist *proplist; + + const char *driver; pa_module *module; pa_client *client; - + pa_sink *sink; - + pa_sample_spec sample_spec; - int sample_spec_is_set; + pa_bool_t sample_spec_is_set; pa_channel_map channel_map; - int channel_map_is_set; + pa_bool_t channel_map_is_set; + pa_cvolume volume; - int volume_is_set; - + 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; 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); +void pa_sink_input_new_data_done(pa_sink_input_new_data *data); + +typedef struct pa_sink_input_move_hook_data { + pa_sink_input *sink_input; + pa_sink *destination; +} pa_sink_input_move_hook_data; + +/* 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_unref(pa_sink_input* i); -pa_sink_input* pa_sink_input_ref(pa_sink_input* i); +void pa_sink_input_put(pa_sink_input *i); +void pa_sink_input_unlink(pa_sink_input* i); -/* To be called by the implementing module only */ -void pa_sink_input_disconnect(pa_sink_input* i); +void pa_sink_input_set_name(pa_sink_input *i, const char *name); -/* External code may request disconnection with this funcion */ -void pa_sink_input_kill(pa_sink_input*i); +pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec); -pa_usec_t pa_sink_input_get_latency(pa_sink_input *i); +/* Request that the specified number of bytes already written out to +the hw device is rewritten, if possible. Please note that this is +only a kind request. The sink driver may not be able to fulfill it +fully -- or at all. If the request for a rewrite was successful, the +sink driver will call ->rewind() and pass the number of bytes that +could be rewound in the HW device. This functionality is required for +implementing the "zero latency" write-through functionality. */ +void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes, pa_bool_t rewrite, pa_bool_t flush); -int pa_sink_input_peek(pa_sink_input *i, pa_memchunk *chunk, pa_cvolume *volume); -void pa_sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length); +void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b); -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); +int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate); -void pa_sink_input_cork(pa_sink_input *i, int b); +/* Callable by everyone from main thread*/ -void pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate); +/* External code may request disconnection with this function */ +void pa_sink_input_kill(pa_sink_input*i); -void pa_sink_input_set_name(pa_sink_input *i, const char *name); +pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency); + +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume); +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); +pa_bool_t pa_sink_input_get_mute(pa_sink_input *i); 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); +int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest); + +pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i); + +pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i); + +/* To be used exclusively by the sink driver IO thread */ + +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); +void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */); +void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */); +void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */); + +void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state); + +int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec); + +pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i); + +pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret); #endif diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 04795e39..edb023b2 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,13 +25,13 @@ #endif #include <stdlib.h> -#include <assert.h> #include <string.h> #include <stdio.h> #include <pulse/introspect.h> #include <pulse/utf8.h> #include <pulse/xmalloc.h> +#include <pulse/timeval.h> #include <pulsecore/sink-input.h> #include <pulsecore/namereg.h> @@ -38,254 +39,609 @@ #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 DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC) + +static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject); + +static void sink_free(pa_object *s); + +pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) { + pa_assert(data); + + memset(data, 0, sizeof(*data)); + data->proplist = pa_proplist_new(); + + return data; +} + +void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name) { + pa_assert(data); + + pa_xfree(data->name); + data->name = pa_xstrdup(name); +} + +void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_sink_new_data_set_muted(pa_sink_new_data *data, pa_bool_t mute) { + pa_assert(data); + + data->muted_is_set = TRUE; + data->muted = !!mute; +} + +void pa_sink_new_data_done(pa_sink_new_data *data) { + pa_assert(data); + + pa_xfree(data->name); + pa_proplist_free(data->proplist); +} -#define CHECK_VALIDITY_RETURN_NULL(condition) \ -do {\ -if (!(condition)) \ - return NULL; \ -} while (0) +/* Called from main context */ +static void reset_callbacks(pa_sink *s) { + pa_assert(s); + + s->set_state = NULL; + s->get_volume = NULL; + s->set_volume = NULL; + s->get_mute = NULL; + s->set_mute = NULL; + s->request_rewind = NULL; + s->update_requested_latency = NULL; +} +/* Called from main context */ 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_new_data *data, + pa_sink_flags_t flags) { + pa_sink *s; - char *n = NULL; - char st[256]; - int r; - pa_channel_map tmap; - - assert(core); - assert(name); - assert(spec); - - CHECK_VALIDITY_RETURN_NULL(pa_sample_spec_valid(spec)); - - if (!map) - map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT); - - CHECK_VALIDITY_RETURN_NULL(map && pa_channel_map_valid(map)); - CHECK_VALIDITY_RETURN_NULL(map->channels == spec->channels); - CHECK_VALIDITY_RETURN_NULL(!driver || pa_utf8_valid(driver)); - CHECK_VALIDITY_RETURN_NULL(pa_utf8_valid(name) && *name); - - s = pa_xnew(pa_sink, 1); - - if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SINK, s, fail))) { + const char *name; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_source_new_data source_data; + const char *dn; + + pa_assert(core); + pa_assert(data); + pa_assert(data->name); + + s = pa_msgobject_new(pa_sink); + + if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SINK, s, data->namereg_fail))) { + pa_xfree(s); + return NULL; + } + + pa_sink_new_data_set_name(data, name); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_NEW], data) < 0) { + pa_xfree(s); + pa_namereg_unregister(core, name); + return NULL; + } + + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); + pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]); + + pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec)); + + if (!data->channel_map_is_set) + pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT)); + + pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map)); + pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels); + + 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 (!data->muted_is_set) + data->muted = FALSE; + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) { pa_xfree(s); + pa_namereg_unregister(core, name); return NULL; } - s->ref = 1; + s->parent.parent.free = sink_free; + s->parent.process_msg = pa_sink_process_msg; + s->core = core; - s->state = PA_SINK_RUNNING; + s->state = PA_SINK_INIT; + s->flags = flags; s->name = pa_xstrdup(name); - s->description = NULL; - s->driver = pa_xstrdup(driver); - s->owner = NULL; + s->proplist = pa_proplist_copy(data->proplist); + s->driver = pa_xstrdup(data->driver); + s->module = data->module; + + s->sample_spec = data->sample_spec; + s->channel_map = data->channel_map; - s->sample_spec = *spec; - s->channel_map = *map; - s->inputs = pa_idxset_new(NULL, NULL); + s->n_corked = 0; + + s->volume = data->volume; + s->muted = data->muted; + s->refresh_volume = s->refresh_muted = FALSE; - pa_cvolume_reset(&s->sw_volume, spec->channels); - pa_cvolume_reset(&s->hw_volume, spec->channels); - s->sw_muted = 0; - s->hw_muted = 0; - - s->is_hardware = 0; - - s->get_latency = NULL; - s->notify = NULL; - s->set_hw_volume = NULL; - s->get_hw_volume = NULL; - s->set_hw_mute = NULL; - s->get_hw_mute = NULL; + reset_callbacks(s); s->userdata = NULL; - r = pa_idxset_put(core->sinks, s, &s->index); - assert(s->index != PA_IDXSET_INVALID && r >= 0); - - pa_sample_spec_snprint(st, sizeof(st), spec); - pa_log_info("created %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); + s->asyncmsgq = NULL; + s->rtpoll = NULL; + + pa_silence_memchunk_get( + &core->silence_cache, + core->mempool, + &s->silence, + &s->sample_spec, + 0); + + s->thread_info.inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_cvolume_reset(&s->thread_info.soft_volume, s->sample_spec.channels); + s->thread_info.soft_muted = FALSE; + s->thread_info.state = s->state; + s->thread_info.rewind_nbytes = 0; + s->thread_info.max_rewind = 0; + s->thread_info.max_request = 0; + s->thread_info.requested_latency_valid = FALSE; + s->thread_info.requested_latency = 0; + s->thread_info.min_latency = DEFAULT_MIN_LATENCY; + s->thread_info.max_latency = 0; + + pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); + + pa_log_info("Created sink %u \"%s\" with sample spec %s and channel map %s", + s->index, + s->name, + pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map)); + + pa_source_new_data_init(&source_data); + pa_source_new_data_set_sample_spec(&source_data, &s->sample_spec); + pa_source_new_data_set_channel_map(&source_data, &s->channel_map); + source_data.name = pa_sprintf_malloc("%s.monitor", name); + source_data.driver = data->driver; + source_data.module = data->module; + + dn = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Monitor of %s", dn ? dn : s->name); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "monitor"); + + s->monitor_source = pa_source_new(core, &source_data, 0); + + pa_source_new_data_done(&source_data); + + if (!s->monitor_source) { + pa_sink_unlink(s); + pa_sink_unref(s); + return NULL; } - pa_xfree(n); - - pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index); - + s->monitor_source->monitor_of = s; + + pa_source_set_latency_range(s->monitor_source, s->thread_info.min_latency, s->thread_info.max_latency); + pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); + return s; } -void pa_sink_disconnect(pa_sink* s) { +/* Called from main context */ +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_IS_OPENED(state)) || + (PA_SINK_IS_OPENED(s->state) && state == PA_SINK_SUSPENDED); + + if (s->set_state) + if ((ret = s->set_state(s, state)) < 0) + return -1; + + if (s->asyncmsgq) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); + + 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; +} + +/* Called from main context */ +void pa_sink_put(pa_sink* s) { + pa_sink_assert_ref(s); + + pa_assert(s->state == PA_SINK_INIT); + + /* The following fields must be initialized properly when calling _put() */ + pa_assert(s->asyncmsgq); + pa_assert(s->rtpoll); + pa_assert(!s->thread_info.min_latency || !s->thread_info.max_latency || + s->thread_info.min_latency <= s->thread_info.max_latency); + + if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) { + s->flags |= PA_SINK_DECIBEL_VOLUME; + + s->thread_info.soft_volume = s->volume; + s->thread_info.soft_muted = s->muted; + } + + 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_PUT], s); +} + +/* Called from main context */ +void pa_sink_unlink(pa_sink* s) { + pa_bool_t linked; pa_sink_input *i, *j = NULL; - - assert(s); - assert(s->state == PA_SINK_RUNNING); - s->state = PA_SINK_DISCONNECTED; - pa_namereg_unregister(s->core, s->name); + 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_IS_LINKED(s->state); + + if (linked) + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK], s); + + if (s->state != PA_SINK_UNLINKED) + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sinks, s, NULL); - pa_hook_fire(&s->core->hook_sink_disconnect, s); - while ((i = pa_idxset_first(s->inputs, NULL))) { - assert(i != j); + pa_assert(i != j); pa_sink_input_kill(i); j = i; } - if (s->monitor_source) - pa_source_disconnect(s->monitor_source); + if (linked) + sink_set_state(s, PA_SINK_UNLINKED); + else + s->state = PA_SINK_UNLINKED; - pa_idxset_remove_by_data(s->core->sinks, s, NULL); + reset_callbacks(s); + + if (s->monitor_source) + pa_source_unlink(s->monitor_source); - s->get_latency = NULL; - s->notify = NULL; - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; - s->set_hw_mute = NULL; - s->get_hw_mute = NULL; - - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); + 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_sink *s) { - assert(s); - assert(!s->ref); - - if (s->state != PA_SINK_DISCONNECTED) - pa_sink_disconnect(s); +/* Called from main context */ +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); - pa_log_info("freed %u \"%s\"", s->index, s->name); + if (PA_SINK_IS_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.memblock) + pa_memblock_unref(s->silence.memblock); + pa_xfree(s->name); - pa_xfree(s->description); pa_xfree(s->driver); + + if (s->proplist) + pa_proplist_free(s->proplist); + pa_xfree(s); } -void pa_sink_unref(pa_sink*s) { - assert(s); - assert(s->ref >= 1); +/* Called from main context */ +void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) { + pa_sink_assert_ref(s); + + s->asyncmsgq = q; + + if (s->monitor_source) + pa_source_set_asyncmsgq(s->monitor_source, q); +} + +/* Called from main context */ +void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) { + pa_sink_assert_ref(s); - if (!(--s->ref)) - sink_free(s); + s->rtpoll = p; + if (s->monitor_source) + pa_source_set_rtpoll(s->monitor_source, p); } -pa_sink* pa_sink_ref(pa_sink *s) { - assert(s); - assert(s->ref >= 1); - - s->ref++; - return s; +/* Called from main context */ +int pa_sink_update_status(pa_sink*s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (s->state == PA_SINK_SUSPENDED) + return 0; + + return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE); } -void pa_sink_notify(pa_sink*s) { - assert(s); - assert(s->ref >= 1); +/* Called from main context */ +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_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); +} + +/* Called from IO thread context */ +void pa_sink_process_rewind(pa_sink *s, size_t nbytes) { + pa_sink_input *i; + void *state = NULL; + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + /* Make sure the sink code already reset the counter! */ + pa_assert(s->thread_info.rewind_nbytes <= 0); - if (s->notify) - s->notify(s); + if (nbytes <= 0) + return; + + pa_log_debug("Processing rewind..."); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { + pa_sink_input_assert_ref(i); + pa_sink_input_process_rewind(i, nbytes); + } + + if (s->monitor_source && PA_SOURCE_IS_OPENED(s->monitor_source->thread_info.state)) + pa_source_process_rewind(s->monitor_source, nbytes); } -static unsigned fill_mix_info(pa_sink *s, pa_mix_info *info, unsigned maxinfo) { - uint32_t idx = PA_IDXSET_INVALID; +/* Called from IO thread context */ +static unsigned fill_mix_info(pa_sink *s, size_t *length, pa_mix_info *info, unsigned maxinfo) { pa_sink_input *i; unsigned n = 0; - - assert(s); - assert(s->ref >= 1); - assert(info); - - for (i = pa_idxset_first(s->inputs, &idx); maxinfo > 0 && i; i = pa_idxset_next(s->inputs, &idx)) { - /* Increase ref counter, to make sure that this input doesn't - * vanish while we still need it */ - pa_sink_input_ref(i); - - if (pa_sink_input_peek(i, &info->chunk, &info->volume) < 0) { - pa_sink_input_unref(i); + void *state = NULL; + size_t mixlength = *length; + + 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; + + if (mixlength == 0 || info->chunk.length < mixlength) + mixlength = info->chunk.length; + + if (pa_memblock_is_silence(info->chunk.memblock)) { + pa_memblock_unref(info->chunk.memblock); continue; } - info->userdata = i; - - assert(info->chunk.memblock); - assert(info->chunk.length); - + info->userdata = pa_sink_input_ref(i); + + pa_assert(info->chunk.memblock); + pa_assert(info->chunk.length > 0); + info++; - maxinfo--; n++; + maxinfo--; } + if (mixlength > 0) + *length = mixlength; + return n; } -static void inputs_drop(pa_sink *s, pa_mix_info *info, unsigned maxinfo, size_t length) { - assert(s); - assert(s->ref >= 1); - assert(info); +/* Called from IO thread context */ +static void inputs_drop(pa_sink *s, pa_mix_info *info, unsigned n, pa_memchunk *result) { + pa_sink_input *i; + void *state = NULL; + unsigned p = 0; + unsigned n_unreffed = 0; + + pa_sink_assert_ref(s); + pa_assert(result); + pa_assert(result->memblock); + pa_assert(result->length > 0); + + /* We optimize for the case where the order of the inputs has not changed */ + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { + unsigned j; + pa_mix_info* m = NULL; + + pa_sink_input_assert_ref(i); - for (; maxinfo > 0; maxinfo--, info++) { - pa_sink_input *i = info->userdata; - - assert(i); - assert(info->chunk.memblock); + /* 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, &info->chunk, length); - pa_memblock_unref(info->chunk.memblock); + pa_sink_input_drop(i, result->length); + + if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source))) { + + if (pa_hashmap_size(i->thread_info.direct_outputs) > 0) { + void *ostate = NULL; + pa_source_output *o; + pa_memchunk c; + + if (m && m->chunk.memblock) { + c = m->chunk; + pa_memblock_ref(c.memblock); + pa_assert(result->length <= c.length); + c.length = result->length; + + pa_memchunk_make_writable(&c, 0); + pa_volume_memchunk(&c, &s->sample_spec, &m->volume); + } else { + c = s->silence; + pa_memblock_ref(c.memblock); + pa_assert(result->length <= c.length); + c.length = result->length; + } + + while ((o = pa_hashmap_iterate(i->thread_info.direct_outputs, &ostate, NULL))) { + pa_source_output_assert_ref(o); + pa_assert(o->direct_on_input == i); + pa_source_post_direct(s->monitor_source, o, &c); + } + + pa_memblock_unref(c.memblock); + } + } - /* Decrease ref counter */ - pa_sink_input_unref(i); - info->userdata = NULL; + if (m) { + if (m->chunk.memblock) + pa_memblock_unref(m->chunk.memblock); + pa_memchunk_reset(&m->chunk); + + pa_sink_input_unref(m->userdata); + m->userdata = NULL; + + n_unreffed += 1; + } } + + /* Now drop references to entries that are included in the + * pa_mix_info array but don't exist anymore */ + + if (n_unreffed < n) { + for (; n > 0; info++, n--) { + if (info->userdata) + pa_sink_input_unref(info->userdata); + if (info->chunk.memblock) + pa_memblock_unref(info->chunk.memblock); + } + } + + if (s->monitor_source && PA_SOURCE_IS_OPENED(pa_source_get_state(s->monitor_source))) + pa_source_post(s->monitor_source, result); } - -int pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { + +/* Called from IO thread context */ +void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { pa_mix_info info[MAX_MIX_CHANNELS]; unsigned n; - int r = -1; - - assert(s); - assert(s->ref >= 1); - assert(length); - assert(result); + size_t block_size_max; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); + pa_assert(pa_frame_aligned(length, &s->sample_spec)); + pa_assert(result); pa_sink_ref(s); - - n = fill_mix_info(s, info, MAX_MIX_CHANNELS); - if (n <= 0) - goto finish; + s->thread_info.rewind_nbytes = 0; + + 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 (n == 1) { + *result = s->silence; + pa_memblock_ref(result->memblock); + + if (result->length > length) + result->length = length; + + } else if (n == 1) { pa_cvolume volume; *result = info[0].chunk; @@ -294,11 +650,12 @@ int pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { if (result->length > length) result->length = length; - pa_sw_cvolume_multiply(&volume, &s->sw_volume, &info[0].volume); - - if (s->sw_muted || !pa_cvolume_is_norm(&volume)) { + 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_log("adjusting volume "); pa_memchunk_make_writable(result, 0); - if (s->sw_muted) + 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); @@ -306,279 +663,869 @@ int pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { } else { void *ptr; result->memblock = pa_memblock_new(s->core->mempool, length); - assert(result->memblock); - -/* pa_log("mixing %i", n); */ ptr = pa_memblock_acquire(result->memblock); - result->length = pa_mix(info, n, ptr, length, &s->sample_spec, &s->sw_volume, s->sw_muted); + result->length = pa_mix(info, n, + ptr, length, + &s->sample_spec, + &s->thread_info.soft_volume, + s->thread_info.soft_muted); pa_memblock_release(result->memblock); - + result->index = 0; } - inputs_drop(s, info, n, result->length); + if (s->thread_info.state == PA_SINK_RUNNING) + inputs_drop(s, info, n, result); - if (s->monitor_source) - pa_source_post(s->monitor_source, result); - - r = 0; - -finish: pa_sink_unref(s); - - return r; } -int pa_sink_render_into(pa_sink*s, pa_memchunk *target) { +/* Called from IO thread context */ +void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { pa_mix_info info[MAX_MIX_CHANNELS]; unsigned n; - int r = -1; - void *ptr; - - assert(s); - assert(s->ref >= 1); - assert(target); - assert(target->memblock); - assert(target->length); + size_t length, block_size_max; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_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 = fill_mix_info(s, info, MAX_MIX_CHANNELS); - if (n <= 0) - goto finish; + s->thread_info.rewind_nbytes = 0; + + length = target->length; + block_size_max = pa_mempool_block_size_max(s->core->mempool); + if (length > block_size_max) + length = pa_frame_align(block_size_max, &s->sample_spec); + + pa_assert(length > 0); - ptr = pa_memblock_acquire(target->memblock); - - if (n == 1) { - void *src; + n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, &length, info, MAX_MIX_CHANNELS) : 0; + + if (n == 0) { + if (target->length > length) + target->length = length; + + pa_silence_memchunk(target, &s->sample_spec); + } else if (n == 1) { pa_cvolume volume; - if (target->length > info[0].chunk.length) - target->length = info[0].chunk.length; + if (target->length > length) + target->length = length; + + pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); - src = pa_memblock_acquire(info[0].chunk.memblock); - - memcpy((uint8_t*) ptr + target->index, - (uint8_t*) src + info[0].chunk.index, - target->length); + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) + pa_silence_memchunk(target, &s->sample_spec); + else { + pa_memchunk vchunk; - pa_memblock_release(info[0].chunk.memblock); + vchunk = info[0].chunk; + pa_memblock_ref(vchunk.memblock); - pa_sw_cvolume_multiply(&volume, &s->sw_volume, &info[0].volume); + if (vchunk.length > length) + vchunk.length = length; + + if (!pa_cvolume_is_norm(&volume)) { + pa_memchunk_make_writable(&vchunk, 0); + pa_volume_memchunk(&vchunk, &s->sample_spec, &volume); + } + + pa_memchunk_memcpy(target, &vchunk); + pa_memblock_unref(vchunk.memblock); + } + + } else { + void *ptr; + + ptr = pa_memblock_acquire(target->memblock); - if (s->sw_muted) - pa_silence_memchunk(target, &s->sample_spec); - else if (!pa_cvolume_is_norm(&volume)) - pa_volume_memchunk(target, &s->sample_spec, &volume); - } else target->length = pa_mix(info, n, - (uint8_t*) ptr + target->index, - target->length, + (uint8_t*) ptr + target->index, length, &s->sample_spec, - &s->sw_volume, - s->sw_muted); + &s->thread_info.soft_volume, + s->thread_info.soft_muted); - pa_memblock_release(target->memblock); - - inputs_drop(s, info, n, target->length); - - if (s->monitor_source) - pa_source_post(s->monitor_source, target); + pa_memblock_release(target->memblock); + } - r = 0; + if (s->thread_info.state == PA_SINK_RUNNING) + inputs_drop(s, info, n, target); -finish: pa_sink_unref(s); - - return r; } +/* Called from IO thread context */ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { pa_memchunk chunk; size_t l, d; - - assert(s); - assert(s->ref >= 1); - assert(target); - assert(target->memblock); - assert(target->length); + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_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); - + + s->thread_info.rewind_nbytes = 0; + l = target->length; d = 0; while (l > 0) { chunk = *target; chunk.index += d; chunk.length -= d; - - if (pa_sink_render_into(s, &chunk) < 0) - break; + + pa_sink_render_into(s, &chunk); d += chunk.length; l -= chunk.length; } - if (l > 0) { - chunk = *target; - chunk.index += d; - chunk.length -= d; - pa_silence_memchunk(&chunk, &s->sample_spec); - } - pa_sink_unref(s); } +/* Called from IO thread context */ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { - assert(s); - assert(s->ref >= 1); - assert(length); - assert(result); + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_OPENED(s->thread_info.state)); + pa_assert(length > 0); + pa_assert(pa_frame_aligned(length, &s->sample_spec)); + pa_assert(result); + + s->thread_info.rewind_nbytes = 0; /*** This needs optimization ***/ - - result->memblock = pa_memblock_new(s->core->mempool, result->length = length); + result->index = 0; + result->length = length; + result->memblock = pa_memblock_new(s->core->mempool, length); pa_sink_render_into_full(s, result); } +/* Called from main thread */ pa_usec_t pa_sink_get_latency(pa_sink *s) { - assert(s); - assert(s->ref >= 1); + pa_usec_t usec = 0; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); - if (!s->get_latency) + /* The returned value is supposed to be in the time domain of the sound card! */ + + if (!PA_SINK_IS_OPENED(s->state)) return 0; - return s->get_latency(s); + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0); + + return usec; } -void pa_sink_set_owner(pa_sink *s, pa_module *m) { - assert(s); - assert(s->ref >= 1); +/* Called from main thread */ +void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume) { + pa_bool_t changed; - if (s->owner == m) - return; - - s->owner = m; + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(volume); - if (s->monitor_source) - pa_source_set_owner(s->monitor_source, m); + changed = !pa_cvolume_equal(volume, &s->volume); + s->volume = *volume; - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); -} + if (s->set_volume && s->set_volume(s) < 0) + s->set_volume = NULL; -void pa_sink_set_volume(pa_sink *s, pa_mixer_t m, const pa_cvolume *volume) { - pa_cvolume *v; - - assert(s); - assert(s->ref >= 1); - assert(volume); + if (!s->set_volume) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, volume, 0, NULL); - if (m == PA_MIXER_HARDWARE && s->set_hw_volume) - v = &s->hw_volume; - else - v = &s->sw_volume; + if (changed) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} - if (pa_cvolume_equal(v, volume)) - return; - - *v = *volume; +/* Called from main thread */ +const pa_cvolume *pa_sink_get_volume(pa_sink *s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); - if (v == &s->hw_volume) - if (s->set_hw_volume(s) < 0) - s->sw_volume = *volume; + if (s->refresh_volume) { + struct pa_cvolume old_volume = s->volume; - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); -} + if (s->get_volume && s->get_volume(s) < 0) + s->get_volume = NULL; -const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_mixer_t m) { - assert(s); - assert(s->ref >= 1); + if (!s->get_volume) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, &s->volume, 0, NULL); - if (m == PA_MIXER_HARDWARE && s->set_hw_volume) { + if (!pa_cvolume_equal(&old_volume, &s->volume)) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } - if (s->get_hw_volume) - s->get_hw_volume(s); - - return &s->hw_volume; - } else - return &s->sw_volume; + return &s->volume; } -void pa_sink_set_mute(pa_sink *s, pa_mixer_t m, int mute) { - int *t; - - assert(s); - assert(s->ref >= 1); +/* Called from main thread */ +void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) { + pa_bool_t changed; - if (m == PA_MIXER_HARDWARE && s->set_hw_mute) - t = &s->hw_muted; - else - t = &s->sw_muted; + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); - if (!!*t == !!mute) - return; - - *t = !!mute; + changed = s->muted != mute; + s->muted = mute; - if (t == &s->hw_muted) - if (s->set_hw_mute(s) < 0) - s->sw_muted = !!mute; + if (s->set_mute && s->set_mute(s) < 0) + s->set_mute = NULL; - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + 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); } -int pa_sink_get_mute(pa_sink *s, pa_mixer_t m) { - assert(s); - assert(s->ref >= 1); +/* Called from main thread */ +pa_bool_t pa_sink_get_mute(pa_sink *s) { + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (s->refresh_muted) { + pa_bool_t old_muted = s->muted; + + if (s->get_mute && s->get_mute(s) < 0) + s->get_mute = NULL; - if (m == PA_MIXER_HARDWARE && s->set_hw_mute) { + if (!s->get_mute) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, &s->muted, 0, NULL); - if (s->get_hw_mute) - s->get_hw_mute(s); - - return s->hw_muted; - } else - return s->sw_muted; + if (old_muted != s->muted) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } + + return s->muted; } +/* Called from main thread */ void pa_sink_set_description(pa_sink *s, const char *description) { - assert(s); - assert(s->ref >= 1); + const char *old; + pa_sink_assert_ref(s); - if (!description && !s->description) + if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION)) return; - if (description && s->description && !strcmp(description, s->description)) + old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (old && description && !strcmp(old, description)) return; - - pa_xfree(s->description); - s->description = pa_xstrdup(description); + + if (description) + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description); + else + pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION); if (s->monitor_source) { char *n; - - n = pa_sprintf_malloc("Monitor Source of %s", s->description? s->description : s->name); + + n = pa_sprintf_malloc("Monitor Source of %s", description ? description : s->name); pa_source_set_description(s->monitor_source, n); pa_xfree(n); } - - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + if (PA_SINK_IS_LINKED(s->state)) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s); + } } -unsigned pa_sink_used_by(pa_sink *s) { +/* Called from main thread */ +unsigned pa_sink_linked_by(pa_sink *s) { unsigned ret; - assert(s); - assert(s->ref >= 1); + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); ret = pa_idxset_size(s->inputs); + /* We add in the number of streams connected to us here. Please + * not the asymmmetry to pa_sink_used_by()! */ + if (s->monitor_source) - ret += pa_source_used_by(s->monitor_source); + ret += pa_source_linked_by(s->monitor_source); + + return ret; +} + +/* Called from main thread */ +unsigned pa_sink_used_by(pa_sink *s) { + unsigned ret; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + ret = pa_idxset_size(s->inputs); + pa_assert(ret >= s->n_corked); + + /* Streams connected to our monitor source do not matter for + * pa_sink_used_by()!.*/ + + return ret - s->n_corked; +} + +/* Called from IO thread, except when it is not */ +int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink *s = PA_SINK(o); + pa_sink_assert_ref(s); + + switch ((pa_sink_message_t) code) { + + case PA_SINK_MESSAGE_ADD_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* If you change anything here, make sure to change the + * sink input handling a few lines down at + * PA_SINK_MESSAGE_FINISH_MOVE, too. */ + + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i)); + + /* Since the caller sleeps in pa_sink_input_put(), we can + * safely access data outside of thread_info even though + * it is mutable */ + + if ((i->thread_info.sync_prev = i->sync_prev)) { + pa_assert(i->sink == i->thread_info.sync_prev->sink); + pa_assert(i->sync_prev->sync_next == i); + i->thread_info.sync_prev->thread_info.sync_next = i; + } + + if ((i->thread_info.sync_next = i->sync_next)) { + pa_assert(i->sink == i->thread_info.sync_next->sink); + pa_assert(i->sync_next->sync_prev == i); + i->thread_info.sync_next->thread_info.sync_prev = i; + } + + pa_assert(!i->thread_info.attached); + i->thread_info.attached = TRUE; + + if (i->attach) + i->attach(i); + + pa_sink_input_set_state_within_thread(i, i->state); + + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + pa_sink_input_update_max_request(i, s->thread_info.max_request); + + pa_sink_invalidate_requested_latency(s); + + /* We don't rewind here automatically. This is left to the + * sink input implementor because some sink inputs need a + * slow start, i.e. need some time to buffer client + * samples before beginning streaming. */ + + 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_PREPAPRE_MOVE, too. */ + + if (i->detach) + i->detach(i); + + pa_sink_input_set_state_within_thread(i, i->state); + + 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); + + pa_sink_invalidate_requested_latency(s); + pa_sink_request_rewind(s, 0); + + return 0; + } + + case PA_SINK_MESSAGE_START_MOVE: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* We don't support moving synchronized streams. */ + pa_assert(!i->sync_prev); + pa_assert(!i->sync_next); + pa_assert(!i->thread_info.sync_next); + pa_assert(!i->thread_info.sync_prev); + + if (i->thread_info.state != PA_SINK_INPUT_CORKED) { + pa_usec_t usec = 0; + size_t sink_nbytes, total_nbytes; + + /* Get the latency of the sink */ + if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec); + total_nbytes = sink_nbytes + pa_memblockq_get_length(i->thread_info.render_memblockq); + + if (total_nbytes > 0) { + i->thread_info.rewrite_nbytes = i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, total_nbytes) : total_nbytes; + i->thread_info.rewrite_flush = TRUE; + pa_sink_input_process_rewind(i, sink_nbytes); + } + } + + if (i->detach) + i->detach(i); + + pa_assert(i->thread_info.attached); + i->thread_info.attached = FALSE; + + /* Let's remove the sink input ...*/ + if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index))) + pa_sink_input_unref(i); + + pa_sink_invalidate_requested_latency(s); + + pa_log_debug("Requesting rewind due to started move"); + pa_sink_request_rewind(s, 0); + + return 0; + } + + case PA_SINK_MESSAGE_FINISH_MOVE: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* We don't support moving synchronized streams. */ + pa_assert(!i->sync_prev); + pa_assert(!i->sync_next); + pa_assert(!i->thread_info.sync_next); + pa_assert(!i->thread_info.sync_prev); + + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i)); + + pa_assert(!i->thread_info.attached); + i->thread_info.attached = TRUE; + + if (i->attach) + i->attach(i); + + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + pa_sink_input_update_max_request(i, s->thread_info.max_request); + + pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency); + + if (i->thread_info.state != PA_SINK_INPUT_CORKED) { + pa_usec_t usec = 0; + size_t nbytes; + + /* Get the latency of the sink */ + if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + nbytes = pa_usec_to_bytes(usec, &s->sample_spec); + + if (nbytes > 0) + pa_sink_input_drop(i, nbytes); + + pa_log_debug("Requesting rewind due to finished move"); + pa_sink_request_rewind(s, nbytes); + } + + return 0; + } + + case PA_SINK_MESSAGE_SET_VOLUME: + s->thread_info.soft_volume = *((pa_cvolume*) userdata); + + pa_sink_request_rewind(s, 0); + return 0; + + case PA_SINK_MESSAGE_SET_MUTE: + s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata); + + pa_sink_request_rewind(s, 0); + 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_SET_STATE: + + s->thread_info.state = PA_PTR_TO_UINT(userdata); + return 0; + + case PA_SINK_MESSAGE_DETACH: + + /* Detach all streams */ + pa_sink_detach_within_thread(s); + return 0; + + case PA_SINK_MESSAGE_ATTACH: + + /* Reattach all streams */ + pa_sink_attach_within_thread(s); + return 0; + + case PA_SINK_MESSAGE_GET_REQUESTED_LATENCY: { + + pa_usec_t *usec = userdata; + *usec = pa_sink_get_requested_latency_within_thread(s); + + if (*usec == (pa_usec_t) -1) + *usec = s->thread_info.max_latency; + + return 0; + } + + case PA_SINK_MESSAGE_SET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + pa_sink_update_latency_range(s, r[0], r[1]); + + return 0; + } + + case PA_SINK_MESSAGE_GET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + r[0] = s->thread_info.min_latency; + r[1] = s->thread_info.max_latency; + + return 0; + } + + case PA_SINK_MESSAGE_GET_MAX_REWIND: + + *((size_t*) userdata) = s->thread_info.max_rewind; + return 0; + + case PA_SINK_MESSAGE_GET_MAX_REQUEST: + + *((size_t*) userdata) = s->thread_info.max_request; + return 0; + + case PA_SINK_MESSAGE_GET_LATENCY: + case PA_SINK_MESSAGE_MAX: + ; + } + + return -1; +} + +/* Called from main thread */ +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; } + +/* Called from main thread */ +void pa_sink_detach(pa_sink *s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_DETACH, NULL, 0, NULL) == 0); +} + +/* Called from main thread */ +void pa_sink_attach(pa_sink *s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_ATTACH, NULL, 0, NULL) == 0); +} + +/* Called from IO thread */ +void pa_sink_detach_within_thread(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_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); +} + +/* Called from IO thread */ +void pa_sink_attach_within_thread(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_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); +} + +/* Called from IO thread */ +void pa_sink_request_rewind(pa_sink*s, size_t nbytes) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + if (nbytes <= 0) + nbytes = s->thread_info.max_rewind; + + nbytes = PA_MIN(nbytes, s->thread_info.max_rewind); + + if (nbytes <= s->thread_info.rewind_nbytes) + return; + + s->thread_info.rewind_nbytes = nbytes; + + if (s->request_rewind) + s->request_rewind(s); +} + +/* Called from IO thread */ +pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { + pa_usec_t result = (pa_usec_t) -1; + pa_sink_input *i; + void *state = NULL; + pa_usec_t monitor_latency; + + pa_sink_assert_ref(s); + + if (s->thread_info.requested_latency_valid) + return s->thread_info.requested_latency; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + + if (i->thread_info.requested_sink_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > i->thread_info.requested_sink_latency)) + result = i->thread_info.requested_sink_latency; + + monitor_latency = pa_source_get_requested_latency_within_thread(s->monitor_source); + + if (monitor_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > monitor_latency)) + result = monitor_latency; + + if (result != (pa_usec_t) -1) { + if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency) + result = s->thread_info.max_latency; + + if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency) + result = s->thread_info.min_latency; + } + + s->thread_info.requested_latency = result; + s->thread_info.requested_latency_valid = TRUE; + + return result; +} + +/* Called from main thread */ +pa_usec_t pa_sink_get_requested_latency(pa_sink *s) { + pa_usec_t usec = 0; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (!PA_SINK_IS_OPENED(s->state)) + return 0; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + return usec; +} + +/* Called from IO thread */ +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + + if (max_rewind == s->thread_info.max_rewind) + return; + + s->thread_info.max_rewind = max_rewind; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + } + + if (s->monitor_source) + pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); +} + +/* Called from IO thread */ +void pa_sink_set_max_request(pa_sink *s, size_t max_request) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + + if (max_request == s->thread_info.max_request) + return; + + s->thread_info.max_request = max_request; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + pa_sink_input_update_max_request(i, s->thread_info.max_request); + } +} + +/* Called from IO thread */ +void pa_sink_invalidate_requested_latency(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + + s->thread_info.requested_latency_valid = FALSE; + + if (s->update_requested_latency) + s->update_requested_latency(s); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->update_sink_requested_latency) + i->update_sink_requested_latency(i); +} + +/* Called from main thread */ +void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_sink_assert_ref(s); + + /* min_latency == 0: no limit + * min_latency == (size_t) -1: default limit + * min_latency anything else: specified limit + * + * Similar for max_latency */ + + if (min_latency == (pa_usec_t) -1) + min_latency = DEFAULT_MIN_LATENCY; + + if (max_latency == (pa_usec_t) -1) + max_latency = min_latency; + + pa_assert(!min_latency || !max_latency || + min_latency <= max_latency); + + if (PA_SINK_IS_LINKED(s->state)) { + pa_usec_t r[2]; + + r[0] = min_latency; + r[1] = max_latency; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); + } else { + s->thread_info.min_latency = min_latency; + s->thread_info.max_latency = max_latency; + + s->monitor_source->thread_info.min_latency = min_latency; + s->monitor_source->thread_info.max_latency = max_latency; + + s->thread_info.requested_latency_valid = s->monitor_source->thread_info.requested_latency_valid = FALSE; + } +} + +/* Called from main thread */ +void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency) { + pa_sink_assert_ref(s); + pa_assert(min_latency); + pa_assert(max_latency); + + if (PA_SINK_IS_LINKED(s->state)) { + pa_usec_t r[2] = { 0, 0 }; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY_RANGE, r, 0, NULL) == 0); + + *min_latency = r[0]; + *max_latency = r[1]; + } else { + *min_latency = s->thread_info.min_latency; + *max_latency = s->thread_info.max_latency; + } +} + +/* Called from IO thread */ +void pa_sink_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + + s->thread_info.min_latency = min_latency; + s->thread_info.max_latency = max_latency; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->update_sink_latency_range) + i->update_sink_latency_range(i); + + pa_sink_invalidate_requested_latency(s); + + pa_source_update_latency_range(s->monitor_source, min_latency, max_latency); +} + +size_t pa_sink_get_max_rewind(pa_sink *s) { + size_t r; + pa_sink_assert_ref(s); + + if (!PA_SINK_IS_LINKED(s->state)) + return s->thread_info.max_rewind; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MAX_REWIND, &r, 0, NULL) == 0); + + return r; +} + +size_t pa_sink_get_max_request(pa_sink *s) { + size_t r; + pa_sink_assert_ref(s); + + if (!PA_SINK_IS_LINKED(s->state)) + return s->thread_info.max_request; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MAX_REQUEST, &r, 0, NULL) == 0); + + return r; +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 1d870620..b73944e8 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -1,108 +1,276 @@ -#ifndef foosinkhfoo -#define foosinkhfoo - -/* $Id$ */ +#ifndef foopulsesinkhfoo +#define foopulsesinkhfoo /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General 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 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_DISCONNECTED + PA_SINK_SUSPENDED, + PA_SINK_IDLE, + PA_SINK_UNLINKED } pa_sink_state_t; +static inline pa_bool_t PA_SINK_IS_OPENED(pa_sink_state_t x) { + return x == PA_SINK_RUNNING || x == PA_SINK_IDLE; +} + +static inline pa_bool_t PA_SINK_IS_LINKED(pa_sink_state_t x) { + return x == PA_SINK_RUNNING || x == PA_SINK_IDLE || x == PA_SINK_SUSPENDED; +} + struct pa_sink { - int ref; + 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 */ - int is_hardware; + char *driver; /* may be NULL */ + pa_proplist *proplist; - pa_module *owner; /* may be NULL */ + pa_module *module; /* may be NULL */ pa_sample_spec sample_spec; pa_channel_map channel_map; pa_idxset *inputs; - pa_source *monitor_source; /* may be NULL */ - - pa_cvolume hw_volume, sw_volume; - int hw_muted, sw_muted; - - void (*notify)(pa_sink*sink); /* may be NULL */ - pa_usec_t (*get_latency)(pa_sink *s); /* dito */ - int (*set_hw_volume)(pa_sink *s); /* dito */ - int (*get_hw_volume)(pa_sink *s); /* dito */ - int (*set_hw_mute)(pa_sink *s); /* dito */ - int (*get_hw_mute)(pa_sink *s); /* dito */ - + unsigned n_corked; + pa_source *monitor_source; + + pa_cvolume volume; + pa_bool_t muted; + + pa_bool_t refresh_volume:1; + pa_bool_t refresh_muted:1; + + pa_asyncmsgq *asyncmsgq; + pa_rtpoll *rtpoll; + + pa_memchunk silence; + + /* Called when the main loop requests a state change. Called from + * main loop context. If returns -1 the state change will be + * inhibited */ + int (*set_state)(pa_sink *s, pa_sink_state_t state); /* may be NULL */ + + /* Callled when the volume is queried. Called from main loop + * context. If this is NULL a PA_SINK_MESSAGE_GET_VOLUME message + * will be sent to the IO thread instead. If refresh_volume is + * FALSE neither this function is called nor a message is sent. */ + int (*get_volume)(pa_sink *s); /* may be NULL */ + + /* Called when the volume shall be changed. Called from main loop + * context. If this is NULL a PA_SINK_MESSAGE_SET_VOLUME message + * will be sent to the IO thread instead. */ + int (*set_volume)(pa_sink *s); /* dito */ + + /* Called when the mute setting is queried. Called from main loop + * context. If this is NULL a PA_SINK_MESSAGE_GET_MUTE message + * will be sent to the IO thread instead. If refresh_mute is + * FALSE neither this function is called nor a message is sent.*/ + int (*get_mute)(pa_sink *s); /* dito */ + + /* Called when the mute setting shall be changed. Called from main + * loop context. If this is NULL a PA_SINK_MESSAGE_SET_MUTE + * message will be sent to the IO thread instead. */ + int (*set_mute)(pa_sink *s); /* dito */ + + /* Called when a rewind request is issued. Called from IO thread + * context. */ + void (*request_rewind)(pa_sink *s); /* dito */ + + /* Called when a the requested latency is changed. Called from IO + * thread context. */ + void (*update_requested_latency)(pa_sink *s); /* dito */ + + /* 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:1; + + pa_bool_t requested_latency_valid:1; + pa_usec_t requested_latency; + + /* The number of bytes streams need to keep around as history to + * be able to satisfy every DMA buffer rewrite */ + size_t max_rewind; + + /* The number of bytes streams need to keep around to satisfy + * every DMA write request */ + size_t max_request; + + /* Maximum of what clients requested to rewind in this cycle */ + size_t rewind_nbytes; + + pa_usec_t min_latency; /* we won't go below this latency */ + pa_usec_t max_latency; /* An upper limit for the latencies */ + } thread_info; + 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_GET_REQUESTED_LATENCY, + PA_SINK_MESSAGE_SET_STATE, + PA_SINK_MESSAGE_START_MOVE, + PA_SINK_MESSAGE_FINISH_MOVE, + PA_SINK_MESSAGE_ATTACH, + PA_SINK_MESSAGE_DETACH, + PA_SINK_MESSAGE_SET_LATENCY_RANGE, + PA_SINK_MESSAGE_GET_LATENCY_RANGE, + PA_SINK_MESSAGE_GET_MAX_REWIND, + PA_SINK_MESSAGE_GET_MAX_REQUEST, + PA_SINK_MESSAGE_MAX +} pa_sink_message_t; + +typedef struct pa_sink_new_data { + char *name; + pa_bool_t namereg_fail; + pa_proplist *proplist; + + const char *driver; + pa_module *module; + + 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_sink_new_data; + +pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data); +void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name); +void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec); +void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map); +void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume); +void pa_sink_new_data_set_muted(pa_sink_new_data *data, pa_bool_t mute); +void pa_sink_new_data_done(pa_sink_new_data *data); + +/* To be called exclusively by the sink driver, from main context */ + pa_sink* pa_sink_new( - pa_core *core, - const char *driver, - const char *name, - int namereg_fail, - const pa_sample_spec *spec, - const pa_channel_map *map); - -void pa_sink_disconnect(pa_sink* s); -void pa_sink_unref(pa_sink*s); -pa_sink* pa_sink_ref(pa_sink *s); - -int pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result); + pa_core *core, + pa_sink_new_data *data, + pa_sink_flags_t flags); + +void pa_sink_put(pa_sink *s); +void pa_sink_unlink(pa_sink* s); + +void pa_sink_set_description(pa_sink *s, const char *description); +void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q); +void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p); + +void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); + +void pa_sink_detach(pa_sink *s); +void pa_sink_attach(pa_sink *s); + +/* May be called by everyone, from main context */ + +/* The returned value is supposed to be in the time domain of the sound card! */ +pa_usec_t pa_sink_get_latency(pa_sink *s); +pa_usec_t pa_sink_get_requested_latency(pa_sink *s); +void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency); + +size_t pa_sink_get_max_rewind(pa_sink *s); +size_t pa_sink_get_max_request(pa_sink *s); + +int pa_sink_update_status(pa_sink*s); +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend); +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend); + +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); -int pa_sink_render_into(pa_sink*s, pa_memchunk *target); +void pa_sink_render_into(pa_sink*s, pa_memchunk *target); void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target); - -pa_usec_t pa_sink_get_latency(pa_sink *s); -void pa_sink_notify(pa_sink*s); +void pa_sink_process_rewind(pa_sink *s, size_t nbytes); -void pa_sink_set_owner(pa_sink *sink, pa_module *m); +int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); -void pa_sink_set_volume(pa_sink *sink, pa_mixer_t m, const pa_cvolume *volume); -const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_mixer_t m); -void pa_sink_set_mute(pa_sink *sink, pa_mixer_t m, int mute); -int pa_sink_get_mute(pa_sink *sink, pa_mixer_t m); +void pa_sink_attach_within_thread(pa_sink *s); +void pa_sink_detach_within_thread(pa_sink *s); -void pa_sink_set_description(pa_sink *s, const char *description); +pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s); + +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind); +void pa_sink_set_max_request(pa_sink *s, size_t max_request); + +void pa_sink_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); + +/* To be called exclusively by sink input drivers, from IO context */ + +void pa_sink_request_rewind(pa_sink*s, size_t nbytes); -unsigned pa_sink_used_by(pa_sink *s); +void pa_sink_invalidate_requested_latency(pa_sink *s); #endif diff --git a/src/pulsecore/sioman.c b/src/pulsecore/sioman.c index d84010ee..7e5b186c 100644 --- a/src/pulsecore/sioman.c +++ b/src/pulsecore/sioman.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -23,21 +23,17 @@ #include <config.h> #endif -#include <assert.h> +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> #include "sioman.h" -static int stdio_inuse = 0; +static pa_atomic_t stdio_inuse = PA_ATOMIC_INIT(0); int pa_stdio_acquire(void) { - if (stdio_inuse) - return -1; - - stdio_inuse = 1; - return 0; + return pa_atomic_cmpxchg(&stdio_inuse, 0, 1) ? 0 : -1; } void pa_stdio_release(void) { - assert(stdio_inuse); - stdio_inuse = 0; -} + pa_assert_se(pa_atomic_cmpxchg(&stdio_inuse, 1, 0)); +} diff --git a/src/pulsecore/sioman.h b/src/pulsecore/sioman.h index cd04d140..d0cacc9b 100644 --- a/src/pulsecore/sioman.h +++ b/src/pulsecore/sioman.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c index 2ceaf5c3..e69a63da 100644 --- a/src/pulsecore/socket-client.c +++ b/src/pulsecore/socket-client.c @@ -1,22 +1,23 @@ -/* $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. + 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 @@ -29,7 +30,6 @@ #include <stdio.h> #include <errno.h> #include <string.h> -#include <assert.h> #include <stdlib.h> #ifdef HAVE_SYS_SOCKET_H @@ -52,31 +52,33 @@ #include <asyncns.h> #endif -#include "winsock.h" - #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/socket-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 { - int ref; + 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); + pa_socket_client_cb_t callback; void *userdata; - int local; + pa_bool_t local; #ifdef HAVE_LIBASYNCNS asyncns_t *asyncns; asyncns_query_t * asyncns_query; @@ -84,20 +86,20 @@ struct pa_socket_client { #endif }; -static pa_socket_client*pa_socket_client_new(pa_mainloop_api *m) { +static pa_socket_client* socket_client_new(pa_mainloop_api *m) { pa_socket_client *c; - assert(m); + pa_assert(m); - c = pa_xmalloc(sizeof(pa_socket_client)); - c->ref = 1; + 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->defer_event = NULL; c->callback = NULL; c->userdata = NULL; - c->local = 0; + c->local = FALSE; #ifdef HAVE_LIBASYNCNS c->asyncns = NULL; @@ -109,35 +111,38 @@ static pa_socket_client*pa_socket_client_new(pa_mainloop_api *m) { } static void free_events(pa_socket_client *c) { - assert(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; } + + if (c->defer_event) { + c->mainloop->defer_free(c->defer_event); + c->defer_event = NULL; + } } static void do_call(pa_socket_client *c) { pa_iochannel *io = NULL; int error; socklen_t lerror; - assert(c && c->callback); + + 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)); @@ -150,45 +155,60 @@ static void do_call(pa_socket_client *c) { } if (error != 0) { - pa_log_debug("connect(): %s", pa_cstrerror(errno)); + pa_log_debug("connect(): %s", pa_cstrerror(error)); errno = error; goto finish; } io = pa_iochannel_new(c->mainloop, c->fd, c->fd); - assert(io); - + pa_assert(io); + finish: if (!io && c->fd >= 0) - close(c->fd); + pa_close(c->fd); c->fd = -1; free_events(c); - - assert(c->callback); + + 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) { +static void connect_defer_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { pa_socket_client *c = userdata; - assert(m && c && c->defer_event == e); + + 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; - assert(m && c && c->io_event == e && fd >= 0); + + 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; - assert(c && sa && len); - - pa_make_nonblock_fd(c->fd); - + + 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) { @@ -200,19 +220,18 @@ static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t return -1; } - c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c); - assert(c->io_event); - } else { - c->defer_event = c->mainloop->defer_new(c->mainloop, connect_fixed_cb, c); - assert(c->defer_event); - } + 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_defer_cb, c)); return 0; } pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port) { struct sockaddr_in sa; - assert(m && port > 0); + + pa_assert(m); + pa_assert(port > 0); memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; @@ -226,12 +245,13 @@ pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) { struct sockaddr_un sa; - assert(m && filename); - + + 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; + pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path)); return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); } @@ -245,37 +265,23 @@ pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *file #endif /* HAVE_SYS_UN_H */ static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size_t salen) { - assert(c); - assert(sa); - 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; - } - + pa_assert(c); + pa_assert(sa); + pa_assert(salen); + + c->local = pa_socket_address_is_local(sa); + if ((c->fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) { pa_log("socket(): %s", pa_cstrerror(errno)); return -1; } - pa_fd_set_cloexec(c->fd, 1); + pa_make_fd_cloexec(c->fd); + if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6) - pa_socket_tcp_low_delay(c->fd); + pa_make_tcp_socket_low_delay(c->fd); else - pa_socket_low_delay(c->fd); + pa_make_socket_low_delay(c->fd); if (do_connect(c, sa, salen) < 0) return -1; @@ -285,29 +291,31 @@ static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen) { pa_socket_client *c; - assert(m && sa); - c = pa_socket_client_new(m); - assert(c); + + pa_assert(m); + pa_assert(sa); + pa_assert(salen > 0); + + pa_assert_se(c = socket_client_new(m)); if (sockaddr_prepare(c, sa, salen) < 0) goto fail; - + return c; fail: pa_socket_client_unref(c); return NULL; - } static void socket_client_free(pa_socket_client *c) { - assert(c && c->mainloop); - + pa_assert(c); + pa_assert(c->mainloop); free_events(c); - + if (c->fd >= 0) - close(c->fd); + pa_close(c->fd); #ifdef HAVE_LIBASYNCNS if (c->asyncns_query) @@ -317,32 +325,41 @@ static void socket_client_free(pa_socket_client *c) { 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) { - assert(c && c->ref >= 1); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); - if (!(--(c->ref))) + if (PA_REFCNT_DEC(c) <= 0) socket_client_free(c); } pa_socket_client* pa_socket_client_ref(pa_socket_client *c) { - assert(c && c->ref >= 1); - c->ref++; + 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) { - assert(c); +void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + c->callback = on_connection; c->userdata = userdata; } pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port) { 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); @@ -357,7 +374,12 @@ static void asyncns_cb(pa_mainloop_api*m, pa_io_event *e, int fd, PA_GCC_UNUSED pa_socket_client *c = userdata; struct addrinfo *res = NULL; int ret; - assert(m && c && c->asyncns_io_event == e && fd >= 0); + + 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; @@ -370,37 +392,38 @@ static void asyncns_cb(pa_mainloop_api*m, pa_io_event *e, int fd, PA_GCC_UNUSED 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; - assert(m); - assert(e); - assert(tv); - assert(c); + + pa_assert(m); + pa_assert(e); + pa_assert(tv); + pa_assert(c); if (c->fd >= 0) { - close(c->fd); + pa_close(c->fd); c->fd = -1; } @@ -410,8 +433,8 @@ static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeva static void start_timeout(pa_socket_client *c) { struct timeval tv; - assert(c); - assert(!c->timeout_event); + pa_assert(c); + pa_assert(!c->timeout_event); pa_gettimeofday(&tv); pa_timeval_add(&tv, CONNECT_TIMEOUT * 1000000); @@ -421,18 +444,20 @@ static void start_timeout(pa_socket_client *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; - assert(m && name); + + 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); + start_timeout(c); break; case PA_PARSED_ADDRESS_TCP4: /* Fallthrough */ @@ -442,44 +467,45 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam struct addrinfo hints; char port[12]; - snprintf(port, sizeof(port), "%u", (unsigned) a.port); + 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 + +#if defined(HAVE_LIBASYNCNS) { asyncns_t *asyncns; - + if (!(asyncns = asyncns_new(1))) goto finish; - c = pa_socket_client_new(m); + pa_assert_se(c = socket_client_new(m)); c->asyncns = asyncns; c->asyncns_io_event = m->io_new(m, asyncns_fd(c->asyncns), PA_IO_EVENT_INPUT, asyncns_cb, c); c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints); - assert(c->asyncns_query); + pa_assert(c->asyncns_query); start_timeout(c); } -#else /* HAVE_LIBASYNCNS */ +#elif defined(HAVE_GETADDRINFO) { -#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 */ + } +#else + { struct hostent *host = NULL; struct sockaddr_in s; @@ -504,8 +530,7 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam 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 */ + start_timeout(c); } #endif /* HAVE_LIBASYNCNS */ } @@ -514,13 +539,15 @@ pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*nam 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) { - assert(c); +pa_bool_t 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 index 47e7cd5a..9ceeaddc 100644 --- a/src/pulsecore/socket-client.h +++ b/src/pulsecore/socket-client.h @@ -1,21 +1,22 @@ #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 @@ -31,17 +32,19 @@ struct sockaddr; typedef struct pa_socket_client pa_socket_client; +typedef void (*pa_socket_client_cb_t)(pa_socket_client *c, pa_iochannel*io, void *userdata); + pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port); pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port); pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename); pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen); pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, 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_unref(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); +void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata); -int pa_socket_client_is_local(pa_socket_client *c); +pa_bool_t pa_socket_client_is_local(pa_socket_client *c); #endif diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c index 4d69b8a4..9885a02b 100644 --- a/src/pulsecore/socket-server.c +++ b/src/pulsecore/socket-server.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,7 +25,6 @@ #endif #include <stdlib.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <sys/types.h> @@ -69,17 +69,19 @@ #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 { - int ref; + PA_REFCNT_DECLARE; int fd; char *filename; char *tcpwrap_service; - void (*on_connection)(pa_socket_server*s, pa_iochannel *io, void *userdata); + pa_socket_server_on_connection_cb_t on_connection; void *userdata; pa_io_event *io_event; @@ -87,23 +89,30 @@ struct pa_socket_server { 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) { +static void callback(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { pa_socket_server *s = userdata; pa_iochannel *io; int nfd; - assert(s && s->mainloop == mainloop && s->io_event == e && e && fd >= 0 && fd == s->fd); + + 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_fd_set_cloexec(nfd, 1); - + pa_make_fd_cloexec(nfd); + if (!s->on_connection) { - close(nfd); + pa_close(nfd); goto finish; } @@ -116,22 +125,21 @@ static void callback(pa_mainloop_api *mainloop, pa_io_event *e, int fd, PA_GCC_U fromhost(&req); if (!hosts_access(&req)) { pa_log_warn("TCP connection refused by tcpwrap."); - close(nfd); + 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_socket_tcp_low_delay(fd); + if (s->type == SOCKET_SERVER_IPV4) + pa_make_tcp_socket_low_delay(fd); else - pa_socket_low_delay(fd); - - io = pa_iochannel_new(s->mainloop, nfd, nfd); - assert(io); + 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: @@ -140,10 +148,12 @@ finish: pa_socket_server* pa_socket_server_new(pa_mainloop_api *m, int fd) { pa_socket_server *s; - assert(m && fd >= 0); - - s = pa_xmalloc(sizeof(pa_socket_server)); - s->ref = 1; + + 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; @@ -151,17 +161,18 @@ pa_socket_server* pa_socket_server_new(pa_mainloop_api *m, int fd) { s->tcpwrap_service = NULL; s->mainloop = m; - s->io_event = m->io_new(m, fd, PA_IO_EVENT_INPUT, callback, s); - assert(s->io_event); + 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) { - assert(s && s->ref >= 1); - s->ref++; + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_REFCNT_INC(s); return s; } @@ -171,21 +182,22 @@ pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *file int fd = -1; struct sockaddr_un sa; pa_socket_server *s; - - assert(m && filename); + + 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_fd_set_cloexec(fd, 1); + pa_make_fd_cloexec(fd); + 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; + pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path)); - pa_socket_low_delay(fd); + pa_make_socket_low_delay(fd); if (bind(fd, (struct sockaddr*) &sa, SUN_LEN(&sa)) < 0) { pa_log("bind(): %s", pa_cstrerror(errno)); @@ -197,23 +209,22 @@ pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *file * 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; } - s = pa_socket_server_new(m, fd); - assert(s); + 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) - close(fd); + pa_close(fd); return NULL; } @@ -232,22 +243,23 @@ pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address struct sockaddr_in sa; int on = 1; - assert(m && port); + 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_fd_set_cloexec(fd, 1); + 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_socket_tcp_low_delay(fd); - + pa_make_tcp_socket_low_delay(fd); + memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); @@ -269,10 +281,10 @@ pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address } return ss; - + fail: if (fd >= 0) - close(fd); + pa_close(fd); return NULL; } @@ -281,28 +293,31 @@ pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t ad pa_socket_server *ss; int fd = -1; struct sockaddr_in6 sa; - int on = 1; + int on; - assert(m && port); + 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_fd_set_cloexec(fd, 1); + pa_make_fd_cloexec(fd); #ifdef IPV6_V6ONLY + on = 1; 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 + on = 1; 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_socket_tcp_low_delay(fd); + pa_make_tcp_socket_low_delay(fd); memset(&sa, 0, sizeof(sa)); sa.sin6_family = AF_INET6; @@ -323,50 +338,50 @@ pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t ad ss->type = SOCKET_SERVER_IPV6; ss->tcpwrap_service = pa_xstrdup(tcpwrap_service); } - + return ss; - + fail: if (fd >= 0) - close(fd); + 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) { - assert(m); - assert(port > 0); + 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) { - assert(m); - assert(port > 0); + 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) { - assert(m); - assert(port > 0); - + 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) { - assert(m); - assert(port > 0); - + 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; - - assert(m); - assert(name); - assert(port > 0); + + 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); @@ -376,10 +391,10 @@ pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const cha 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; - - assert(m); - assert(name); - assert(port > 0); + + 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); @@ -388,14 +403,14 @@ pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const cha } static void socket_server_free(pa_socket_server*s) { - assert(s); + pa_assert(s); if (s->filename) { unlink(s->filename); pa_xfree(s->filename); } - close(s->fd); + pa_close(s->fd); pa_xfree(s->tcpwrap_service); @@ -404,22 +419,27 @@ static void socket_server_free(pa_socket_server*s) { } void pa_socket_server_unref(pa_socket_server *s) { - assert(s && s->ref >= 1); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); - if (!(--(s->ref))) + 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) { - assert(s && s->ref >= 1); +void pa_socket_server_set_callback(pa_socket_server*s, pa_socket_server_on_connection_cb_t on_connection, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); s->on_connection = on_connection; s->userdata = userdata; } char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { - assert(s && c && l > 0); - + 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; @@ -434,24 +454,24 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { char fqdn[256]; if (!pa_get_fqdn(fqdn, sizeof(fqdn))) return NULL; - - snprintf(c, l, "tcp6:%s:%u", fqdn, (unsigned) ntohs(sa.sin6_port)); - + + 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; - - snprintf(c, l, "{%s}tcp6:localhost:%u", hn, (unsigned) ntohs(sa.sin6_port)); + + 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; } - - snprintf(c, l, "tcp6:[%s]:%u", ip, (unsigned) ntohs(sa.sin6_port)); + + pa_snprintf(c, l, "tcp6:[%s]:%u", ip, (unsigned) ntohs(sa.sin6_port)); } return c; @@ -470,14 +490,14 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { char fqdn[256]; if (!pa_get_fqdn(fqdn, sizeof(fqdn))) return NULL; - - snprintf(c, l, "tcp:%s:%u", fqdn, (unsigned) ntohs(sa.sin_port)); + + 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; - - snprintf(c, l, "{%s}tcp:localhost:%u", hn, (unsigned) ntohs(sa.sin_port)); + + pa_snprintf(c, l, "{%s}tcp:localhost:%u", hn, (unsigned) ntohs(sa.sin_port)); } else { char ip[INET_ADDRSTRLEN]; @@ -485,11 +505,10 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { pa_log("inet_ntop(): %s", pa_cstrerror(errno)); return NULL; } - - snprintf(c, l, "tcp:[%s]:%u", ip, (unsigned) ntohs(sa.sin_port)); + pa_snprintf(c, l, "tcp:[%s]:%u", ip, (unsigned) ntohs(sa.sin_port)); } - + return c; } @@ -498,11 +517,11 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { if (!s->filename) return NULL; - + if (!pa_get_host_name(hn, sizeof(hn))) return NULL; - snprintf(c, l, "{%s}unix:%s", hn, s->filename); + pa_snprintf(c, l, "{%s}unix:%s", hn, s->filename); return c; } diff --git a/src/pulsecore/socket-server.h b/src/pulsecore/socket-server.h index d90c8194..1edfb432 100644 --- a/src/pulsecore/socket-server.h +++ b/src/pulsecore/socket-server.h @@ -1,21 +1,22 @@ #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 @@ -44,7 +45,9 @@ pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const cha 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); +typedef void (*pa_socket_server_on_connection_cb_t)(pa_socket_server*s, pa_iochannel *io, void *userdata); + +void pa_socket_server_set_callback(pa_socket_server*s, pa_socket_server_on_connection_cb_t connection_cb, void *userdata); char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l); diff --git a/src/pulsecore/socket-util.c b/src/pulsecore/socket-util.c index 856c28e8..f721f699 100644 --- a/src/pulsecore/socket-util.c +++ b/src/pulsecore/socket-util.c @@ -1,18 +1,20 @@ -/* $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 @@ -27,7 +29,6 @@ #include <stdlib.h> #include <signal.h> #include <errno.h> -#include <assert.h> #include <string.h> #include <stdio.h> #include <sys/types.h> @@ -71,24 +72,24 @@ #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; - assert(c && l && fd >= 0); - + pa_assert(fd >= 0); + pa_assert(c); + pa_assert(l > 0); + #ifndef OS_IS_WIN32 - if (fstat(fd, &st) < 0) { - snprintf(c, l, "Invalid client fd"); - return; - } + pa_assert_se(fstat(fd, &st) == 0); #endif #ifndef OS_IS_WIN32 if (S_ISSOCK(st.st_mode)) { -#endif +#endif union { struct sockaddr sa; struct sockaddr_in in; @@ -98,18 +99,18 @@ void pa_socket_peer_to_string(int fd, char *c, size_t l) { #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); - - 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)); + + 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]; @@ -117,94 +118,107 @@ void pa_socket_peer_to_string(int fd, char *c, size_t l) { res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)); if (res) { - snprintf(c, l, "TCP/IP client from [%s]:%u", buf, ntohs(sa.in6.sin6_port)); + 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) { - snprintf(c, l, "UNIX socket client"); + pa_snprintf(c, l, "UNIX socket client"); return; #endif } - } + #ifndef OS_IS_WIN32 - snprintf(c, l, "Unknown network client"); + pa_snprintf(c, l, "Unknown network client"); return; } else if (S_ISCHR(st.st_mode) && (fd == 0 || fd == 1)) { - snprintf(c, l, "STDIN/STDOUT client"); + pa_snprintf(c, l, "STDIN/STDOUT client"); return; } #endif /* OS_IS_WIN32 */ - snprintf(c, l, "Unknown client"); + pa_snprintf(c, l, "Unknown client"); } -int pa_socket_low_delay(int fd) { +void pa_make_socket_low_delay(int fd) { + #ifdef SO_PRIORITY int priority; - assert(fd >= 0); + pa_assert(fd >= 0); - priority = 7; + priority = 6; if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (void*)&priority, sizeof(priority)) < 0) - return -1; + pa_log_warn("SO_PRIORITY failed: %s", pa_cstrerror(errno)); #endif - - return 0; } -int pa_socket_tcp_low_delay(int fd) { - int ret, tos, on; +void pa_make_tcp_socket_low_delay(int fd) { + pa_assert(fd >= 0); - assert(fd >= 0); - - ret = pa_socket_low_delay(fd); - - on = 1; - tos = 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) + 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) + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) < 0) #endif - ret = -1; + pa_log_warn("TCP_NODELAY failed: %s", pa_cstrerror(errno)); + } #endif -#if defined(IPTOS_LOWDELAY) && defined(IP_TOS) && (defined(SOL_IP) || \ - defined(IPPROTO_IP)) - tos = IPTOS_LOWDELAY; +#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) + 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) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) #endif - ret = -1; + pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno)); + } #endif +} - return ret; +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) { - assert(fd >= 0); + pa_assert(fd >= 0); -/* if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&l, sizeof(l)) < 0) { */ -/* pa_log("SO_RCVBUF: %s", strerror(errno)); */ -/* return -1; */ -/* } */ + 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) { - assert(fd >= 0); + pa_assert(fd >= 0); -/* if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&l, sizeof(l)) < 0) { */ -/* pa_log("SO_SNDBUF: %s", strerror(errno)); */ -/* return -1; */ -/* } */ + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&l, sizeof(l)) < 0) { + pa_log("SO_SNDBUF: %s", pa_cstrerror(errno)); + return -1; + } return 0; } @@ -215,6 +229,8 @@ 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; @@ -232,20 +248,22 @@ int pa_unix_socket_is_stale(const char *fn) { finish: if (fd >= 0) - close(fd); + 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; @@ -264,3 +282,40 @@ int pa_unix_socket_remove_stale(const char *fn) { } #endif /* HAVE_SYS_UN_H */ + + +pa_bool_t pa_socket_address_is_local(const struct sockaddr *sa) { + pa_assert(sa); + + switch (sa->sa_family) { + case AF_UNIX: + return TRUE; + + case AF_INET: + return ((const struct sockaddr_in*) sa)->sin_addr.s_addr == INADDR_LOOPBACK; + + case AF_INET6: + return memcmp(&((const struct sockaddr_in6*) sa)->sin6_addr, &in6addr_loopback, sizeof(struct in6_addr)) == 0; + + default: + return FALSE; + } +} + +pa_bool_t pa_socket_is_local(int fd) { + + 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) + return FALSE; + + return pa_socket_address_is_local(&sa.sa); +} diff --git a/src/pulsecore/socket-util.h b/src/pulsecore/socket-util.h index f8248ae7..7a40285a 100644 --- a/src/pulsecore/socket-util.h +++ b/src/pulsecore/socket-util.h @@ -1,21 +1,22 @@ #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 @@ -23,11 +24,15 @@ ***/ #include <sys/types.h> +#include <sys/socket.h> + +#include <pulsecore/macro.h> void pa_socket_peer_to_string(int fd, char *c, size_t l); -int pa_socket_low_delay(int fd); -int pa_socket_tcp_low_delay(int fd); +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); @@ -35,4 +40,7 @@ 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); +pa_bool_t pa_socket_address_is_local(const struct sockaddr *sa); +pa_bool_t pa_socket_is_local(int fd); + #endif diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index d2ffeeed..8eedf830 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2008 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,135 +24,272 @@ #endif #include <stdlib.h> -#include <assert.h> #include <stdio.h> #include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> #include <sndfile.h> #include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulsecore/core-error.h> #include <pulsecore/sink-input.h> #include <pulsecore/log.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sample-util.h> #include "sound-file-stream.h" -#define BUF_SIZE (1024*10) +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) -struct userdata { - SNDFILE *sndfile; +typedef struct file_stream { + pa_msgobject parent; + pa_core *core; pa_sink_input *sink_input; - pa_memchunk memchunk; + + SNDFILE *sndfile; sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames); + + /* We need this memblockq here to easily fulfill rewind requests + * (even beyond the file start!) */ + pa_memblockq *memblockq; +} file_stream; + +enum { + FILE_STREAM_MESSAGE_UNLINK }; -static void free_userdata(struct userdata *u) { - assert(u); - if (u->sink_input) { - pa_sink_input_disconnect(u->sink_input); - pa_sink_input_unref(u->sink_input); - } - - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); +PA_DECLARE_CLASS(file_stream); +#define FILE_STREAM(o) (file_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(file_stream, pa_msgobject); + +/* Called from main context */ +static void file_stream_unlink(file_stream *u) { + pa_assert(u); + + if (!u->sink_input) + return; + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + /* Make sure we don't decrease the ref count twice. */ + file_stream_unref(u); +} + +/* Called from main context */ +static void file_stream_free(pa_object *o) { + file_stream *u = FILE_STREAM(o); + pa_assert(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + if (u->sndfile) sf_close(u->sndfile); pa_xfree(u); } -static void sink_input_kill(pa_sink_input *i) { - assert(i && i->userdata); - free_userdata(i->userdata); +/* Called from main context */ +static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + file_stream *u = FILE_STREAM(o); + file_stream_assert_ref(u); + + switch (code) { + case FILE_STREAM_MESSAGE_UNLINK: + file_stream_unlink(u); + break; + } + + return 0; } -static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) { - struct userdata *u; - assert(i && chunk && i->userdata); - u = i->userdata; +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + file_stream *u; - if (!u->memchunk.memblock) { - uint32_t fs = pa_frame_size(&i->sample_spec); - sf_count_t n; + pa_sink_input_assert_ref(i); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + file_stream_unlink(u); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + file_stream *u; + + pa_sink_input_assert_ref(i); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); +} + +/* Called from IO thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + file_stream *u; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + if (!u->memblockq) + return -1; + + for (;;) { + pa_memchunk tchunk; + size_t fs; void *p; + sf_count_t n; - u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, BUF_SIZE); - u->memchunk.index = 0; + if (pa_memblockq_peek(u->memblockq, chunk) >= 0) { + chunk->length = PA_MIN(chunk->length, length); + pa_memblockq_drop(u->memblockq, chunk->length); + return 0; + } - p = pa_memblock_acquire(u->memchunk.memblock); - - if (u->readf_function) { - if ((n = u->readf_function(u->sndfile, p, BUF_SIZE/fs)) <= 0) - n = 0; + if (!u->sndfile) + break; + + tchunk.memblock = pa_memblock_new(i->sink->core->mempool, length); + tchunk.index = 0; - u->memchunk.length = n * fs; + p = pa_memblock_acquire(tchunk.memblock); + + if (u->readf_function) { + fs = pa_frame_size(&i->sample_spec); + n = u->readf_function(u->sndfile, p, length/fs); } else { - if ((n = sf_read_raw(u->sndfile, p, BUF_SIZE)) <= 0) - n = 0; - - u->memchunk.length = n; + fs = 1; + n = sf_read_raw(u->sndfile, p, length); } - pa_memblock_release(u->memchunk.memblock); - - if (!u->memchunk.length) { - free_userdata(u); - return -1; + pa_memblock_release(tchunk.memblock); + + if (n <= 0) { + pa_memblock_unref(tchunk.memblock); + + sf_close(u->sndfile); + u->sndfile = NULL; + break; } + + tchunk.length = n * fs; + + pa_memblockq_push(u->memblockq, &tchunk); + pa_memblock_unref(tchunk.memblock); } - *chunk = u->memchunk; - pa_memblock_ref(chunk->memblock); - assert(chunk->length); - return 0; + if (pa_sink_input_safe_to_remove(i)) { + pa_memblockq_free(u->memblockq); + u->memblockq = NULL; + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL); + } + + return -1; + } + +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + file_stream *u; + + pa_sink_input_assert_ref(i); + pa_assert(nbytes > 0); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + pa_log("backwards %lu", (unsigned long) nbytes); + + if (!u->memblockq) + return; + + pa_memblockq_rewind(u->memblockq, nbytes); } -static void sink_input_drop(pa_sink_input *i, const pa_memchunk*chunk, size_t length) { - struct userdata *u; - assert(i && chunk && length && i->userdata); - u = i->userdata; +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + file_stream *u; - assert(!memcmp(chunk, &u->memchunk, sizeof(chunk))); - assert(length <= u->memchunk.length); + pa_sink_input_assert_ref(i); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); - u->memchunk.index += length; - u->memchunk.length -= length; + if (!u->memblockq) + return; - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; - } + pa_memblockq_set_maxrewind(u->memblockq, nbytes); } int pa_play_file( pa_sink *sink, const char *fname, const pa_cvolume *volume) { - - struct userdata *u = NULL; + + file_stream *u = NULL; SF_INFO sfinfo; pa_sample_spec ss; pa_sink_input_new_data data; - - assert(sink); - assert(fname); + int fd; + + pa_assert(sink); + pa_assert(fname); - u = pa_xnew(struct userdata, 1); + u = pa_msgobject_new(file_stream); + u->parent.parent.free = file_stream_free; + u->parent.process_msg = file_stream_process_msg; + u->core = sink->core; u->sink_input = NULL; - u->memchunk.memblock = NULL; - u->memchunk.index = u->memchunk.length = 0; u->sndfile = NULL; + u->readf_function = NULL; + u->memblockq = NULL; memset(&sfinfo, 0, sizeof(sfinfo)); - if (!(u->sndfile = sf_open(fname, SFM_READ, &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; } - u->readf_function = NULL; - switch (sfinfo.format & 0xFF) { case SF_FORMAT_PCM_16: case SF_FORMAT_PCM_U8: @@ -164,7 +301,7 @@ int pa_play_file( case SF_FORMAT_ULAW: ss.format = PA_SAMPLE_ULAW; break; - + case SF_FORMAT_ALAW: ss.format = PA_SAMPLE_ALAW; break; @@ -175,7 +312,7 @@ int pa_play_file( 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; @@ -187,25 +324,36 @@ int pa_play_file( 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))) + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname)); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname); + + u->sink_input = pa_sink_input_new(sink->core, &data, 0); + pa_sink_input_new_data_done(&data); + + if (!u->sink_input) goto fail; - u->sink_input->peek = sink_input_peek; - u->sink_input->drop = sink_input_drop; - u->sink_input->kill = sink_input_kill; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->userdata = u; - - pa_sink_notify(u->sink_input->sink); + + u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL); + + 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) - free_userdata(u); - + file_stream_unref(u); + return -1; } diff --git a/src/pulsecore/sound-file-stream.h b/src/pulsecore/sound-file-stream.h index 28e6a8ba..4cc69146 100644 --- a/src/pulsecore/sound-file-stream.h +++ b/src/pulsecore/sound-file-stream.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c index c74a1586..3183ede6 100644 --- a/src/pulsecore/sound-file.c +++ b/src/pulsecore/sound-file.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,35 +24,63 @@ #endif #include <string.h> -#include <assert.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; +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; - - assert(fname); - assert(ss); - assert(chunk); + int fd; - chunk->memblock = NULL; - chunk->index = chunk->length = 0; + pa_assert(fname); + pa_assert(ss); + pa_assert(chunk); + pa_memchunk_reset(chunk); memset(&sfinfo, 0, sizeof(sfinfo)); - if (!(sf = sf_open(fname, SFM_READ, &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; } @@ -67,7 +95,7 @@ int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, case SF_FORMAT_ULAW: ss->format = PA_SAMPLE_ULAW; break; - + case SF_FORMAT_ALAW: ss->format = PA_SAMPLE_ALAW; break; @@ -89,26 +117,25 @@ int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, } if (map) - pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_DEFAULT); - - if ((l = pa_frame_size(ss)*sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_DEFAULT); + + 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); - assert(chunk->memblock); 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) != l)) { + (!readf_function && sf_read_raw(sf, ptr, l) != (sf_count_t) l)) { pa_log("Premature file end"); goto finish; } - + ret = 0; finish: @@ -118,22 +145,24 @@ finish: 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 0; + return -1; } sf_close(sf); @@ -148,7 +177,7 @@ int pa_sound_file_too_big_to_cache(const char *fname) { case SF_FORMAT_ULAW: ss.format = PA_SAMPLE_ULAW; break; - + case SF_FORMAT_ALAW: ss.format = PA_SAMPLE_ALAW; break; @@ -163,8 +192,13 @@ int pa_sound_file_too_big_to_cache(const char *fname) { 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); + pa_log("File too large: %s", fname); return 1; } diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h index 7e3c82ea..e4d703d3 100644 --- a/src/pulsecore/sound-file.h +++ b/src/pulsecore/sound-file.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 352fce14..3d1abe30 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,336 +24,741 @@ #endif #include <stdio.h> -#include <assert.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/namereg.h> +#include <pulsecore/core-util.h> #include "source-output.h" -#define CHECK_VALIDITY_RETURN_NULL(condition) \ -do {\ -if (!(condition)) \ - return NULL; \ -} while (0) +#define MEMBLOCKQ_MAXLENGTH (32*1024*1024) + +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) { - assert(data); - + pa_assert(data); + memset(data, 0, sizeof(*data)); data->resample_method = PA_RESAMPLER_INVALID; + data->proplist = pa_proplist_new(); + return data; } +void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map) { - assert(data); + 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) { - assert(data); +void pa_source_output_new_data_done(pa_source_output_new_data *data) { + pa_assert(data); - if ((data->sample_spec_is_set = !!spec)) - data->sample_spec = *spec; + pa_proplist_free(data->proplist); } +/* Called from main context */ +static void reset_callbacks(pa_source_output *o) { + pa_assert(o); + + o->push = NULL; + o->process_rewind = NULL; + o->update_max_rewind = NULL; + o->update_source_requested_latency = NULL; + o->update_source_latency_range = NULL; + o->attach = NULL; + o->detach = NULL; + o->suspend = NULL; + o->moved = NULL; + o->kill = NULL; + o->get_latency = NULL; + o->state_change = NULL; +} + +/* Called from main context */ 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; - int r; - char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; - assert(core); - assert(data); + pa_assert(core); + pa_assert(data); - if (!(flags & PA_SOURCE_OUTPUT_NO_HOOKS)) - if (pa_hook_fire(&core->hook_source_output_new, data) < 0) - return NULL; + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data) < 0) + return NULL; - CHECK_VALIDITY_RETURN_NULL(!data->driver || pa_utf8_valid(data->driver)); - CHECK_VALIDITY_RETURN_NULL(!data->name || pa_utf8_valid(data->name)); + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); if (!data->source) - data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE, 1); + data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE, TRUE); + + pa_return_null_if_fail(data->source); + pa_return_null_if_fail(pa_source_get_state(data->source) != PA_SOURCE_UNLINKED); + + pa_return_null_if_fail(!data->direct_on_input || data->direct_on_input->sink == data->source->monitor_of); - CHECK_VALIDITY_RETURN_NULL(data->source); - CHECK_VALIDITY_RETURN_NULL(data->source->state == PA_SOURCE_RUNNING); - if (!data->sample_spec_is_set) data->sample_spec = data->source->sample_spec; - - CHECK_VALIDITY_RETURN_NULL(pa_sample_spec_valid(&data->sample_spec)); - if (!data->channel_map_is_set) - pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); - - CHECK_VALIDITY_RETURN_NULL(pa_channel_map_valid(&data->channel_map)); - CHECK_VALIDITY_RETURN_NULL(data->channel_map.channels == data->sample_spec.channels); + 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; - CHECK_VALIDITY_RETURN_NULL(data->resample_method < PA_RESAMPLER_MAX); - + 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 (!pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) || - !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) + 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))) { + 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; } - - o = pa_xnew(pa_source_output, 1); - o->ref = 1; - o->state = PA_SOURCE_OUTPUT_RUNNING; - o->name = pa_xstrdup(data->name); + + 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->proplist = pa_proplist_copy(data->proplist); 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->direct_on_input = data->direct_on_input; + + reset_callbacks(o); o->userdata = NULL; - - o->resampler = resampler; - o->resample_method = data->resample_method; - - r = pa_idxset_put(core->source_outputs, o, &o->index); - assert(r == 0); - r = pa_idxset_put(o->source->outputs, o, NULL); - assert(r == 0); - pa_log_info("created %u \"%s\" on %s with sample spec %s", + o->thread_info.state = o->state; + o->thread_info.attached = FALSE; + o->thread_info.sample_spec = o->sample_spec; + o->thread_info.resampler = resampler; + o->thread_info.requested_source_latency = (pa_usec_t) -1; + o->thread_info.direct_on_input = o->direct_on_input; + + o->thread_info.delay_memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + pa_frame_size(&o->source->sample_spec), + 0, + 1, + 0, + &o->source->silence); + + pa_assert_se(pa_idxset_put(core->source_outputs, o, &o->index) == 0); + pa_assert_se(pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL) == 0); + + if (o->direct_on_input) + pa_assert_se(pa_idxset_put(o->direct_on_input->direct_outputs, o, NULL) == 0); + + pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s", o->index, - o->name, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)), o->source->name, - pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec)); - - pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index); - - /* We do not call pa_source_notify() here, because the virtual - * functions have not yet been initialized */ - - return o; + 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; } -void pa_source_output_disconnect(pa_source_output*o) { - assert(o); - assert(o->state != PA_SOURCE_OUTPUT_DISCONNECTED); - assert(o->source); - assert(o->source->core); - +/* Called from main context */ +static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) { + pa_assert(o); + + 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); +} + +/* Called from main context */ +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; + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); + + update_n_corked(o, state); + 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; +} + +/* Called from main context */ +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_IS_LINKED(o->state); + + if (linked) + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o); + + if (o->direct_on_input) + pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL); pa_idxset_remove_by_data(o->source->core->source_outputs, o, NULL); - pa_idxset_remove_by_data(o->source->outputs, o, NULL); + if (pa_idxset_remove_by_data(o->source->outputs, o, NULL)) + pa_source_output_unref(o); - pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index); - o->source = NULL; + update_n_corked(o, PA_SOURCE_OUTPUT_UNLINKED); + o->state = PA_SOURCE_OUTPUT_UNLINKED; - o->push = NULL; - o->kill = NULL; - o->get_latency = NULL; - - o->state = PA_SOURCE_OUTPUT_DISCONNECTED; + if (linked) + if (o->source->asyncmsgq) + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0); + + reset_callbacks(o); + + if (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_source_output* o) { - assert(o); +/* Called from main context */ +static void source_output_free(pa_object* mo) { + pa_source_output *o = PA_SOURCE_OUTPUT(mo); + + pa_assert(o); + pa_assert(pa_source_output_refcnt(o) == 0); + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + pa_source_output_unlink(o); + + pa_log_info("Freeing output %u \"%s\"", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME))); + + pa_assert(!o->thread_info.attached); + + if (o->thread_info.delay_memblockq) + pa_memblockq_free(o->thread_info.delay_memblockq); - if (o->state != PA_SOURCE_OUTPUT_DISCONNECTED) - pa_source_output_disconnect(o); + if (o->thread_info.resampler) + pa_resampler_free(o->thread_info.resampler); - pa_log_info("freed %u \"%s\"", o->index, o->name); - - if (o->resampler) - pa_resampler_free(o->resampler); + if (o->proplist) + pa_proplist_free(o->proplist); - pa_xfree(o->name); pa_xfree(o->driver); pa_xfree(o); } -void pa_source_output_unref(pa_source_output* o) { - assert(o); - assert(o->ref >= 1); +/* Called from main context */ +void pa_source_output_put(pa_source_output *o) { + pa_source_output_state_t state; + pa_source_output_assert_ref(o); - if (!(--o->ref)) - source_output_free(o); -} + pa_assert(o->state == PA_SOURCE_OUTPUT_INIT); -pa_source_output* pa_source_output_ref(pa_source_output *o) { - assert(o); - assert(o->ref >= 1); - - o->ref++; - return o; + /* The following fields must be initialized properly */ + pa_assert(o->push); + pa_assert(o->kill); + + state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING; + + update_n_corked(o, state); + o->state = state; + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0); + + pa_subscription_post(o->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); } +/* Called from main context */ void pa_source_output_kill(pa_source_output*o) { - assert(o); - assert(o->ref >= 1); + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); - if (o->kill) - o->kill(o); + o->kill(o); } +/* Called from main context */ +pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency) { + pa_usec_t r[2] = { 0, 0 }; + + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0); + + if (o->get_latency) + r[0] += o->get_latency(o); + + if (source_latency) + *source_latency = r[1]; + + return r[0]; +} + +/* Called from thread context */ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { - pa_memchunk rchunk; - - assert(o); - assert(chunk); - assert(chunk->length); - assert(o->push); - - if (o->state == PA_SOURCE_OUTPUT_CORKED) - return; - - if (!o->resampler) { - o->push(o, chunk); + size_t length; + size_t limit, mbs = 0; + + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); + pa_assert(chunk); + pa_assert(pa_frame_aligned(chunk->length, &o->source->sample_spec)); + + if (!o->push || o->thread_info.state == PA_SOURCE_OUTPUT_CORKED) return; + + pa_assert(o->thread_info.state == PA_SOURCE_OUTPUT_RUNNING); + + if (pa_memblockq_push(o->thread_info.delay_memblockq, chunk) < 0) { + pa_log_debug("Delay queue overflow!"); + pa_memblockq_seek(o->thread_info.delay_memblockq, chunk->length, PA_SEEK_RELATIVE); } - pa_resampler_run(o->resampler, chunk, &rchunk); - if (!rchunk.length) - return; - - assert(rchunk.memblock); - o->push(o, &rchunk); - pa_memblock_unref(rchunk.memblock); + limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind; + + /* Implement the delay queue */ + while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) { + pa_memchunk qchunk; + + length -= limit; + + pa_assert_se(pa_memblockq_peek(o->thread_info.delay_memblockq, &qchunk) >= 0); + + if (qchunk.length > length) + qchunk.length = length; + + pa_assert(qchunk.length > 0); + + if (!o->thread_info.resampler) + o->push(o, &qchunk); + else { + pa_memchunk rchunk; + + if (mbs == 0) + mbs = pa_resampler_max_block_size(o->thread_info.resampler); + + if (qchunk.length > mbs) + qchunk.length = mbs; + + pa_resampler_run(o->thread_info.resampler, &qchunk, &rchunk); + + if (rchunk.length > 0) + o->push(o, &rchunk); + + if (rchunk.memblock) + pa_memblock_unref(rchunk.memblock); + } + + pa_memblock_unref(qchunk.memblock); + pa_memblockq_drop(o->thread_info.delay_memblockq, qchunk.length); + } } -void pa_source_output_set_name(pa_source_output *o, const char *name) { - assert(o); - assert(o->ref >= 1); +/* Called from thread context */ +void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in sink sample spec */) { + pa_source_output_assert_ref(o); - if (!o->name && !name) - return; + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec)); - if (o->name && name && !strcmp(o->name, name)) + if (nbytes <= 0) return; - - pa_xfree(o->name); - o->name = pa_xstrdup(name); - pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + if (o->process_rewind) { + pa_assert(pa_memblockq_get_length(o->thread_info.delay_memblockq) == 0); + + if (o->thread_info.resampler) + nbytes = pa_resampler_result(o->thread_info.resampler, nbytes); + + pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) nbytes); + + if (nbytes > 0) + o->process_rewind(o, nbytes); + + if (o->thread_info.resampler) + pa_resampler_reset(o->thread_info.resampler); + + } else + pa_memblockq_rewind(o->thread_info.delay_memblockq, nbytes); } -pa_usec_t pa_source_output_get_latency(pa_source_output *o) { - assert(o); - assert(o->ref >= 1); - - if (o->get_latency) - return o->get_latency(o); +/* Called from thread context */ +void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes /* in the source's sample spec */) { + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec)); + + if (o->update_max_rewind) + o->update_max_rewind(o, o->thread_info.resampler ? pa_resampler_result(o->thread_info.resampler, nbytes) : nbytes); +} + +/* Called from thread context */ +static pa_usec_t fixup_latency(pa_source *s, pa_usec_t usec) { + pa_source_assert_ref(s); + + if (usec == (pa_usec_t) -1) + return usec; + + if (s->thread_info.max_latency > 0 && usec > s->thread_info.max_latency) + usec = s->thread_info.max_latency; + + if (s->thread_info.min_latency > 0 && usec < s->thread_info.min_latency) + usec = s->thread_info.min_latency; + + return usec; +} + +/* Called from thread context */ +pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) { + pa_source_output_assert_ref(o); + + usec = fixup_latency(o->source, usec); + o->thread_info.requested_source_latency = usec; + pa_source_invalidate_requested_latency(o->source); + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) { + pa_source_output_assert_ref(o); + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + else { + /* If this sink input is not realized yet, we have to touch + * the thread info data directly */ + + usec = fixup_latency(o->source, usec); + o->thread_info.requested_source_latency = usec; + o->source->thread_info.requested_latency_valid = FALSE; + } + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) { + pa_usec_t usec = 0; + + pa_source_output_assert_ref(o); + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + else + /* If this sink input is not realized yet, we have to touch + * the thread info data directly */ + usec = o->thread_info.requested_source_latency; + + return usec; +} + +/* Called from main context */ +void pa_source_output_cork(pa_source_output *o, pa_bool_t b) { + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING); +} + +/* Called from main context */ +int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) { + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_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_cork(pa_source_output *o, int b) { - int n; - - assert(o); - assert(o->ref >= 1); +/* Called from main context */ +void pa_source_output_set_name(pa_source_output *o, const char *name) { + const char *old; + pa_source_output_assert_ref(o); + + if (!name && !pa_proplist_contains(o->proplist, PA_PROP_MEDIA_NAME)) + return; + + old = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME); - if (o->state == PA_SOURCE_OUTPUT_DISCONNECTED) + if (old && name && !strcmp(old, name)) return; - n = o->state == PA_SOURCE_OUTPUT_CORKED && !b; - - o->state = b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING; - - if (n) - pa_source_notify(o->source); + if (name) + pa_proplist_sets(o->proplist, PA_PROP_MEDIA_NAME, name); + else + pa_proplist_unset(o->proplist, PA_PROP_MEDIA_NAME); + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) { + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o); + pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + } } +/* Called from main context */ pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) { - assert(o); - assert(o->ref >= 1); - - if (!o->resampler) - return o->resample_method; + pa_source_output_assert_ref(o); - return pa_resampler_get_method(o->resampler); + return o->resample_method; } +/* Called from main context */ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) { pa_source *origin; - pa_resampler *new_resampler = NULL; + pa_resampler *new_resampler; + pa_source_output_move_hook_data hook_data; - assert(o); - assert(o->ref >= 1); - assert(dest); + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_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 (o->direct_on_input) + 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->resampler && + 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->resampler; - - else if (!pa_sample_spec_equal(&o->sample_spec, &dest->sample_spec) || - !pa_channel_map_equal(&o->channel_map, &dest->channel_map)) { + 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 */ - /* Okey, we need a new resampler for the new sink */ - 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->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; } - } + } else + new_resampler = NULL; + + 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_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0); + 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->resampler) { - if (o->resampler) - pa_resampler_free(o->resampler); - o->resampler = new_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_memblockq_free(o->thread_info.delay_memblockq); + + o->thread_info.delay_memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + pa_frame_size(&o->source->sample_spec), + 0, + 1, + 0, + &o->source->silence); } + pa_source_update_status(origin); + pa_source_update_status(dest); + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0); + + 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); - pa_source_notify(o->source); return 0; } + +/* Called from IO thread context */ +void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state) { + pa_source_output_assert_ref(o); + + if (state == o->thread_info.state) + return; + + if (o->state_change) + o->state_change(o, state); + + o->thread_info.state = state; +} + +/* Called from IO thread context, except when it is not */ +int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk* chunk) { + pa_source_output *o = PA_SOURCE_OUTPUT(mo); + pa_source_output_assert_ref(o); + + switch (code) { + + case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + pa_usec_t source_usec = 0; + + r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec); + + if (o->source->parent.process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_usec, 0, NULL) >= 0) + r[1] += source_usec; + + return 0; + } + + case PA_SOURCE_OUTPUT_MESSAGE_SET_RATE: + + o->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata); + pa_resampler_set_output_rate(o->thread_info.resampler, PA_PTR_TO_UINT(userdata)); + return 0; + + case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE: + + pa_source_output_set_state_within_thread(o, PA_PTR_TO_UINT(userdata)); + return 0; + + case PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY: { + pa_usec_t *usec = userdata; + + *usec = pa_source_output_set_requested_latency_within_thread(o, *usec); + + return 0; + } + + case PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY: { + pa_usec_t *r = userdata; + + *r = o->thread_info.requested_source_latency; + return 0; + } + } + + return -1; +} diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index f7396a19..61825b22 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -1,21 +1,21 @@ -#ifndef foosourceoutputhfoo -#define foosourceoutputhfoo - -/* $Id$ */ +#ifndef foopulsesourceoutputhfoo +#define foopulsesourceoutputhfoo /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -33,51 +33,151 @@ typedef struct pa_source_output pa_source_output; #include <pulsecore/module.h> #include <pulsecore/client.h> -typedef enum { +typedef enum pa_source_output_state { + PA_SOURCE_OUTPUT_INIT, PA_SOURCE_OUTPUT_RUNNING, PA_SOURCE_OUTPUT_CORKED, - PA_SOURCE_OUTPUT_DISCONNECTED + PA_SOURCE_OUTPUT_UNLINKED } pa_source_output_state_t; +static inline pa_bool_t PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_state_t x) { + return x == PA_SOURCE_OUTPUT_RUNNING || x == PA_SOURCE_OUTPUT_CORKED; +} + typedef enum pa_source_output_flags { - PA_SOURCE_OUTPUT_NO_HOOKS = 1 + 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 { - int ref; + pa_msgobject parent; + uint32_t index; + pa_core *core; + pa_source_output_state_t state; - - char *name, *driver; /* may be NULL */ + pa_source_output_flags_t flags; + + char *driver; /* may be NULL */ + pa_proplist *proplist; + pa_module *module; /* may be NULL */ + pa_client *client; /* may be NULL */ pa_source *source; - pa_client *client; /* may be NULL */ + + /* A source output can monitor just a single input of a sink, in which case we find it here */ + pa_sink_input *direct_on_input; /* may be NULL */ pa_sample_spec sample_spec; pa_channel_map channel_map; - - void (*push)(pa_source_output *o, const pa_memchunk *chunk); - void (*kill)(pa_source_output* o); /* may be NULL */ - pa_usec_t (*get_latency) (pa_source_output *o); /* may be NULL */ - pa_resampler* resampler; /* may be NULL */ pa_resample_method_t resample_method; - + + /* Pushes a new memchunk into the output. Called from IO thread + * context. */ + void (*push)(pa_source_output *o, const pa_memchunk *chunk); /* may NOT be NULL */ + + /* Only relevant for monitor sources right now: called when the + * recorded stream is rewound. Called from IO context */ + void (*process_rewind)(pa_source_output *o, size_t nbytes); /* may be NULL */ + + /* Called whenever the maximum rewindable size of the source + * changes. Called from IO thread context. */ + void (*update_max_rewind) (pa_source_output *o, size_t nbytes); /* may be NULL */ + + /* Called whenever the configured latency of the source + * changes. Called from IO context. */ + void (*update_source_requested_latency) (pa_source_output *o); /* may be NULL */ + + /* Called whenver the latency range of the source changes. Called + * from IO context. */ + void (*update_source_latency_range) (pa_source_output *o); /* may be NULL */ + + /* 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 suspends or resumes. Called from main context */ + void (*suspend) (pa_source_output *o, pa_bool_t b); /* may be NULL */ + + /* If non-NULL called whenever the the source this output is attached + * to changes. Called from main context */ + void (*moved) (pa_source_output *o); /* may be NULL */ + + /* Supposed to unlink and destroy this stream. Called from main + * context. */ + void (*kill)(pa_source_output* o); /* may NOT be NULL */ + + /* Return the current latency (i.e. length of bufferd audio) of + this stream. Called from main context. This is added to what the + PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY message sent to the IO thread + returns */ + pa_usec_t (*get_latency) (pa_source_output *o); /* may be NULL */ + + /* If non_NULL this function is called from thread context if the + * state changes. The old state is found in thread_info.state. */ + void (*state_change) (pa_source_output *o, pa_source_output_state_t state); /* may be NULL */ + + 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 */ + + /* We maintain a delay memblockq here for source outputs that + * don't implement rewind() */ + pa_memblockq *delay_memblockq; + + /* The requested latency for the source */ + pa_usec_t requested_source_latency; + + pa_sink_input *direct_on_input; /* may be NULL */ + } thread_info; + void *userdata; }; +PA_DECLARE_CLASS(pa_source_output); +#define PA_SOURCE_OUTPUT(o) pa_source_output_cast(o) + +enum { + PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, + PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, + PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, + PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, + PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, + PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + typedef struct pa_source_output_new_data { - const char *name, *driver; + pa_proplist *proplist; + pa_sink_input *direct_on_input; + + const char *driver; pa_module *module; pa_client *client; pa_source *source; pa_sample_spec sample_spec; - int sample_spec_is_set; + pa_bool_t sample_spec_is_set; pa_channel_map channel_map; - int channel_map_is_set; + pa_bool_t channel_map_is_set; pa_resample_method_t resample_method; } pa_source_output_new_data; @@ -85,32 +185,56 @@ typedef struct pa_source_output_new_data { pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data); void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec); void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map); -void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume); +void pa_source_output_new_data_done(pa_source_output_new_data *data); + +typedef struct pa_source_output_move_hook_data { + pa_source_output *source_output; + pa_source *destination; +} pa_source_output_move_hook_data; + +/* 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_unref(pa_source_output* o); -pa_source_output* pa_source_output_ref(pa_source_output *o); +void pa_source_output_put(pa_source_output *o); +void pa_source_output_unlink(pa_source_output*o); -/* To be called by the implementing module only */ -void pa_source_output_disconnect(pa_source_output*o); +void pa_source_output_set_name(pa_source_output *i, const char *name); -/* External code may request disconnection with this funcion */ -void pa_source_output_kill(pa_source_output*o); +pa_usec_t pa_source_output_set_requested_latency(pa_source_output *i, pa_usec_t usec); -void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk); +void pa_source_output_cork(pa_source_output *i, pa_bool_t b); -void pa_source_output_set_name(pa_source_output *i, const char *name); +int pa_source_output_set_rate(pa_source_output *o, uint32_t rate); -pa_usec_t pa_source_output_get_latency(pa_source_output *i); +/* Callable by everyone */ -void pa_source_output_cork(pa_source_output *i, int b); +/* 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, pa_usec_t *source_latency); 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) + +pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o); + +/* To be used exclusively by the source driver thread */ + +void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk); +void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes); +void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes); + +void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state); + +int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec); + #endif diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index c48d6aaa..8256a988 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -1,18 +1,19 @@ -/* $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 @@ -24,12 +25,12 @@ #endif #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <string.h> #include <pulse/utf8.h> #include <pulse/xmalloc.h> +#include <pulse/timeval.h> #include <pulsecore/source-output.h> #include <pulsecore/namereg.h> @@ -39,306 +40,946 @@ #include "source.h" -#define CHECK_VALIDITY_RETURN_NULL(condition) \ -do {\ -if (!(condition)) \ - return NULL; \ -} while (0) +#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC) + +static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject); + +static void source_free(pa_object *o); + +pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) { + pa_assert(data); + + memset(data, 0, sizeof(*data)); + data->proplist = pa_proplist_new(); + + return data; +} + +void pa_source_new_data_set_name(pa_source_new_data *data, const char *name) { + pa_assert(data); + + pa_xfree(data->name); + data->name = pa_xstrdup(name); +} + +void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map) { + pa_assert(data); + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_source_new_data_set_muted(pa_source_new_data *data, pa_bool_t mute) { + pa_assert(data); + + data->muted_is_set = TRUE; + data->muted = !!mute; +} + +void pa_source_new_data_done(pa_source_new_data *data) { + pa_assert(data); + + pa_xfree(data->name); + pa_proplist_free(data->proplist); +} + +/* Called from main context */ +static void reset_callbacks(pa_source *s) { + pa_assert(s); + + s->set_state = NULL; + s->get_volume = NULL; + s->set_volume = NULL; + s->get_mute = NULL; + s->set_mute = NULL; + s->update_requested_latency = NULL; +} + +/* Called from main context */ 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_new_data *data, + pa_source_flags_t flags) { + pa_source *s; - char st[256]; - int r; - pa_channel_map tmap; - - assert(core); - assert(name); - assert(spec); + const char *name; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_assert(core); + pa_assert(data); + pa_assert(data->name); + + s = pa_msgobject_new(pa_source); + + if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SOURCE, s, data->namereg_fail))) { + pa_xfree(s); + return NULL; + } + + pa_source_new_data_set_name(data, name); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_NEW], data) < 0) { + pa_xfree(s); + pa_namereg_unregister(core, name); + return NULL; + } + + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); + pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]); - CHECK_VALIDITY_RETURN_NULL(pa_sample_spec_valid(spec)); + pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec)); - if (!map) - map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT); + if (!data->channel_map_is_set) + pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT)); - CHECK_VALIDITY_RETURN_NULL(map && pa_channel_map_valid(map)); - CHECK_VALIDITY_RETURN_NULL(map->channels == spec->channels); - CHECK_VALIDITY_RETURN_NULL(!driver || pa_utf8_valid(driver)); - CHECK_VALIDITY_RETURN_NULL(pa_utf8_valid(name) && *name); + 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); - s = pa_xnew(pa_source, 1); + if (!data->volume_is_set) + pa_cvolume_reset(&data->volume, data->sample_spec.channels); - if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SOURCE, s, fail))) { + pa_return_null_if_fail(pa_cvolume_valid(&data->volume)); + pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels); + + if (!data->muted_is_set) + data->muted = FALSE; + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) { pa_xfree(s); + pa_namereg_unregister(core, name); return NULL; } - s->ref = 1; + s->parent.parent.free = source_free; + s->parent.process_msg = pa_source_process_msg; + s->core = core; - s->state = PA_SOURCE_RUNNING; + s->state = PA_SOURCE_INIT; + s->flags = flags; s->name = pa_xstrdup(name); - s->description = NULL; - s->driver = pa_xstrdup(driver); - s->owner = NULL; - - s->sample_spec = *spec; - s->channel_map = *map; + s->proplist = pa_proplist_copy(data->proplist); + s->driver = pa_xstrdup(data->driver); + s->module = data->module; + + s->sample_spec = data->sample_spec; + s->channel_map = data->channel_map; s->outputs = pa_idxset_new(NULL, NULL); + s->n_corked = 0; s->monitor_of = NULL; - pa_cvolume_reset(&s->sw_volume, spec->channels); - pa_cvolume_reset(&s->hw_volume, spec->channels); - s->sw_muted = 0; - s->hw_muted = 0; - - s->is_hardware = 0; + s->volume = data->volume; + s->muted = data->muted; + s->refresh_volume = s->refresh_muted = FALSE; - s->get_latency = NULL; - s->notify = NULL; - s->set_hw_volume = NULL; - s->get_hw_volume = NULL; - s->set_hw_mute = NULL; - s->get_hw_mute = NULL; + reset_callbacks(s); s->userdata = NULL; - r = pa_idxset_put(core->sources, s, &s->index); - assert(s->index != PA_IDXSET_INVALID && r >= 0); + s->asyncmsgq = NULL; + s->rtpoll = NULL; + + pa_silence_memchunk_get( + &core->silence_cache, + core->mempool, + &s->silence, + &s->sample_spec, + 0); + + s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_cvolume_reset(&s->thread_info.soft_volume, s->sample_spec.channels); + s->thread_info.soft_muted = FALSE; + s->thread_info.state = s->state; + s->thread_info.max_rewind = 0; + s->thread_info.requested_latency_valid = FALSE; + s->thread_info.requested_latency = 0; + s->thread_info.min_latency = DEFAULT_MIN_LATENCY; + s->thread_info.max_latency = 0; + + pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0); + + pa_log_info("Created source %u \"%s\" with sample spec %s and channel map %s", + s->index, + s->name, + pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map)); - pa_sample_spec_snprint(st, sizeof(st), spec); - pa_log_info("created %u \"%s\" with sample spec \"%s\"", s->index, s->name, st); - - pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index); - return s; } -void pa_source_disconnect(pa_source *s) { +/* Called from main context */ +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_IS_OPENED(state)) || + (PA_SOURCE_IS_OPENED(s->state) && state == PA_SOURCE_SUSPENDED); + + if (s->set_state) + if ((ret = s->set_state(s, state)) < 0) + return -1; + + if (s->asyncmsgq) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); + + 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; +} + +/* Called from main context */ +void pa_source_put(pa_source *s) { + pa_source_assert_ref(s); + + pa_assert(s->state == PA_SINK_INIT); + + /* The following fields must be initialized properly when calling _put() */ + pa_assert(s->asyncmsgq); + pa_assert(s->rtpoll); + pa_assert(!s->thread_info.min_latency || !s->thread_info.max_latency || + s->thread_info.min_latency <= s->thread_info.max_latency); + + if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) { + s->flags |= PA_SOURCE_DECIBEL_VOLUME; + + s->thread_info.soft_volume = s->volume; + s->thread_info.soft_muted = s->muted; + } + + 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_PUT], s); +} + +/* Called from main context */ +void pa_source_unlink(pa_source *s) { + pa_bool_t linked; pa_source_output *o, *j = NULL; - - assert(s); - assert(s->state == PA_SOURCE_RUNNING); - s->state = PA_SOURCE_DISCONNECTED; - pa_namereg_unregister(s->core, s->name); + pa_assert(s); + + /* See pa_sink_unlink() for a couple of comments how this function + * works. */ + + linked = PA_SOURCE_IS_LINKED(s->state); + + if (linked) + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], s); + + if (s->state != PA_SOURCE_UNLINKED) + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sources, s, NULL); - pa_hook_fire(&s->core->hook_source_disconnect, s); - while ((o = pa_idxset_first(s->outputs, NULL))) { - assert(o != j); + pa_assert(o != j); pa_source_output_kill(o); j = o; } - pa_idxset_remove_by_data(s->core->sources, s, NULL); + if (linked) + source_set_state(s, PA_SOURCE_UNLINKED); + else + s->state = PA_SOURCE_UNLINKED; + + reset_callbacks(s); + + if (linked) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], s); + } +} + +/* Called from main context */ +static void source_free(pa_object *o) { + pa_source_output *so; + pa_source *s = PA_SOURCE(o); + + pa_assert(s); + pa_assert(pa_source_refcnt(s) == 0); + + if (PA_SOURCE_IS_LINKED(s->state)) + pa_source_unlink(s); - s->get_latency = NULL; - s->notify = NULL; - s->get_hw_volume = NULL; - s->set_hw_volume = NULL; - s->set_hw_mute = NULL; - s->get_hw_mute = NULL; - - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); -} - -static void source_free(pa_source *s) { - assert(s); - assert(!s->ref); - - if (s->state != PA_SOURCE_DISCONNECTED) - pa_source_disconnect(s); - - pa_log_info("freed %u \"%s\"", s->index, s->name); + 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); + + if (s->silence.memblock) + pa_memblock_unref(s->silence.memblock); + pa_xfree(s->name); - pa_xfree(s->description); pa_xfree(s->driver); + + if (s->proplist) + pa_proplist_free(s->proplist); + pa_xfree(s); } -void pa_source_unref(pa_source *s) { - assert(s); - assert(s->ref >= 1); +/* Called from main context */ +void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) { + pa_source_assert_ref(s); - if (!(--s->ref)) - source_free(s); + s->asyncmsgq = q; } -pa_source* pa_source_ref(pa_source *s) { - assert(s); - assert(s->ref >= 1); - - s->ref++; - return s; +/* Called from main context */ +void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) { + pa_source_assert_ref(s); + + s->rtpoll = p; +} + +/* Called from main context */ +int pa_source_update_status(pa_source*s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (s->state == PA_SOURCE_SUSPENDED) + return 0; + + return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE); } -void pa_source_notify(pa_source*s) { - assert(s); - assert(s->ref >= 1); +/* Called from main context */ +int pa_source_suspend(pa_source *s, pa_bool_t suspend) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); - if (s->notify) - s->notify(s); + 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); } -static int do_post(void *p, PA_GCC_UNUSED uint32_t idx, PA_GCC_UNUSED int *del, void*userdata) { - pa_source_output *o = p; - const pa_memchunk *chunk = userdata; - - assert(o); - assert(chunk); +/* Called from IO thread context */ +void pa_source_process_rewind(pa_source *s, size_t nbytes) { + pa_source_output *o; + void *state = NULL; - pa_source_output_push(o, chunk); - return 0; + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state)); + + if (nbytes <= 0) + return; + + pa_log_debug("Processing rewind..."); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { + pa_source_output_assert_ref(o); + pa_source_output_process_rewind(o, nbytes); + } } +/* Called from IO thread context */ void pa_source_post(pa_source*s, const pa_memchunk *chunk) { - assert(s); - assert(s->ref >= 1); - assert(chunk); + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state)); + pa_assert(chunk); - pa_source_ref(s); + if (s->thread_info.state != PA_SOURCE_RUNNING) + return; - if (s->sw_muted || !pa_cvolume_is_norm(&s->sw_volume)) { + 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->sw_muted) + + 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->sw_volume); - pa_idxset_foreach(s->outputs, do_post, &vchunk); + pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { + pa_source_output_assert_ref(o); + + if (!o->thread_info.direct_on_input) + pa_source_output_push(o, &vchunk); + } + pa_memblock_unref(vchunk.memblock); - } else - pa_idxset_foreach(s->outputs, do_post, (void*) chunk); + } else { - pa_source_unref(s); + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { + pa_source_output_assert_ref(o); + + if (!o->thread_info.direct_on_input) + pa_source_output_push(o, chunk); + } + } } -void pa_source_set_owner(pa_source *s, pa_module *m) { - assert(s); - assert(s->ref >= 1); +/* Called from IO thread context */ +void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_OPENED(s->thread_info.state)); + pa_source_output_assert_ref(o); + pa_assert(o->thread_info.direct_on_input); + pa_assert(chunk); - if (m == s->owner) + if (s->thread_info.state != PA_SOURCE_RUNNING) return; - - s->owner = m; - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&s->thread_info.soft_volume)) { + pa_memchunk vchunk = *chunk; + + pa_memblock_ref(vchunk.memblock); + pa_memchunk_make_writable(&vchunk, 0); + + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&s->thread_info.soft_volume)) + pa_silence_memchunk(&vchunk, &s->sample_spec); + else + pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume); + + pa_source_output_push(o, &vchunk); + + pa_memblock_unref(vchunk.memblock); + } else + pa_source_output_push(o, chunk); } +/* Called from main thread */ pa_usec_t pa_source_get_latency(pa_source *s) { - assert(s); - assert(s->ref >= 1); + pa_usec_t usec; - if (!s->get_latency) + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (!PA_SOURCE_IS_OPENED(s->state)) return 0; - return s->get_latency(s); + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0); + + return usec; } -void pa_source_set_volume(pa_source *s, pa_mixer_t m, const pa_cvolume *volume) { - pa_cvolume *v; - - assert(s); - assert(s->ref >= 1); - assert(volume); +/* Called from main thread */ +void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) { + pa_bool_t changed; - if (m == PA_MIXER_HARDWARE && s->set_hw_volume) - v = &s->hw_volume; - else - v = &s->sw_volume; + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(volume); - if (pa_cvolume_equal(v, volume)) - return; - - *v = *volume; + changed = !pa_cvolume_equal(volume, &s->volume); + s->volume = *volume; - if (v == &s->hw_volume) - if (s->set_hw_volume(s) < 0) - s->sw_volume = *volume; + if (s->set_volume && s->set_volume(s) < 0) + s->set_volume = NULL; - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + if (!s->set_volume) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, volume, 0, NULL); + + 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_mixer_t m) { - assert(s); - assert(s->ref >= 1); +/* Called from main thread */ +const pa_cvolume *pa_source_get_volume(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); - if (m == PA_MIXER_HARDWARE && s->set_hw_volume) { + if (s->refresh_volume) { + pa_cvolume old_volume = s->volume; - if (s->get_hw_volume) - s->get_hw_volume(s); - - return &s->hw_volume; - } else - return &s->sw_volume; + if (s->get_volume && s->get_volume(s) < 0) + s->get_volume = NULL; + + if (!s->get_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_mixer_t m, int mute) { - int *t; - - assert(s); - assert(s->ref >= 1); +/* Called from main thread */ +void pa_source_set_mute(pa_source *s, pa_bool_t mute) { + pa_bool_t changed; - if (m == PA_MIXER_HARDWARE && s->set_hw_mute) - t = &s->hw_muted; - else - t = &s->sw_muted; + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); - if (!!*t == !!mute) - return; - - *t = !!mute; + changed = s->muted != mute; + s->muted = mute; + + if (s->set_mute && s->set_mute(s) < 0) + s->set_mute = NULL; - if (t == &s->hw_muted) - if (s->set_hw_mute(s) < 0) - s->sw_muted = !!mute; + 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); - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + if (changed) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } -int pa_source_get_mute(pa_source *s, pa_mixer_t m) { - assert(s); - assert(s->ref >= 1); +/* Called from main thread */ +pa_bool_t pa_source_get_mute(pa_source *s) { - if (m == PA_MIXER_HARDWARE && s->set_hw_mute) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); - if (s->get_hw_mute) - s->get_hw_mute(s); - - return s->hw_muted; - } else - return s->sw_muted; + if (s->refresh_muted) { + pa_bool_t old_muted = s->muted; + + if (s->get_mute && s->get_mute(s) < 0) + s->get_mute = NULL; + + if (!s->get_mute) + 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; } +/* Called from main thread */ void pa_source_set_description(pa_source *s, const char *description) { - assert(s); - assert(s->ref >= 1); + const char *old; + pa_source_assert_ref(s); - if (!description && !s->description) + if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION)) return; - if (description && s->description && !strcmp(description, s->description)) + old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (old && description && !strcmp(old, description)) return; - - pa_xfree(s->description); - s->description = pa_xstrdup(description); - pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + if (description) + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description); + else + pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (PA_SOURCE_IS_LINKED(s->state)) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s); + } } -unsigned pa_source_used_by(pa_source *s) { - assert(s); - assert(s->ref >= 1); +/* Called from main thread */ +unsigned pa_source_linked_by(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); return pa_idxset_size(s->outputs); } + +/* Called from main thread */ +unsigned pa_source_used_by(pa_source *s) { + unsigned ret; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + ret = pa_idxset_size(s->outputs); + pa_assert(ret >= s->n_corked); + + return ret - s->n_corked; +} + +/* Called from IO thread, except when it is not */ +int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_source *s = PA_SOURCE(object); + pa_source_assert_ref(s); + + switch ((pa_source_message_t) code) { + + case PA_SOURCE_MESSAGE_ADD_OUTPUT: { + pa_source_output *o = PA_SOURCE_OUTPUT(userdata); + + pa_hashmap_put(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index), pa_source_output_ref(o)); + + if (o->direct_on_input) { + o->thread_info.direct_on_input = o->direct_on_input; + pa_hashmap_put(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index), o); + } + + pa_assert(!o->thread_info.attached); + o->thread_info.attached = TRUE; + + if (o->attach) + o->attach(o); + + pa_source_output_set_state_within_thread(o, o->state); + + pa_source_output_update_max_rewind(o, s->thread_info.max_rewind); + + /* We don't just invalidate the requested latency here, + * because if we are in a move we might need to fix up the + * requested latency. */ + pa_source_output_set_requested_latency_within_thread(o, o->thread_info.requested_source_latency); + + return 0; + } + + case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: { + pa_source_output *o = PA_SOURCE_OUTPUT(userdata); + + pa_source_output_set_state_within_thread(o, o->state); + + if (o->detach) + o->detach(o); + + pa_assert(o->thread_info.attached); + o->thread_info.attached = FALSE; + + if (o->thread_info.direct_on_input) { + pa_hashmap_remove(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index)); + o->thread_info.direct_on_input = NULL; + } + + if (pa_hashmap_remove(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index))) + pa_source_output_unref(o); + + pa_source_invalidate_requested_latency(s); + + 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_SET_STATE: + s->thread_info.state = PA_PTR_TO_UINT(userdata); + return 0; + + case PA_SOURCE_MESSAGE_DETACH: + + /* Detach all streams */ + pa_source_detach_within_thread(s); + return 0; + + case PA_SOURCE_MESSAGE_ATTACH: + + /* Reattach all streams */ + pa_source_attach_within_thread(s); + return 0; + + case PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY: { + + pa_usec_t *usec = userdata; + *usec = pa_source_get_requested_latency_within_thread(s); + + if (*usec == (pa_usec_t) -1) + *usec = s->thread_info.max_latency; + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + pa_source_update_latency_range(s, r[0], r[1]); + + return 0; + } + + case PA_SOURCE_MESSAGE_GET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + r[0] = s->thread_info.min_latency; + r[1] = s->thread_info.max_latency; + + return 0; + } + + case PA_SOURCE_MESSAGE_GET_MAX_REWIND: + + *((size_t*) userdata) = s->thread_info.max_rewind; + return 0; + + case PA_SOURCE_MESSAGE_GET_LATENCY: + + if (s->monitor_of) { + *((pa_usec_t*) userdata) = 0; + return 0; + } + + /* Implementors need to overwrite this implementation! */ + return -1; + + case PA_SOURCE_MESSAGE_MAX: + ; + } + + return -1; +} + +/* Called from main thread */ +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; +} + +/* Called from main thread */ +void pa_source_detach(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_DETACH, NULL, 0, NULL) == 0); +} + +/* Called from main thread */ +void pa_source_attach(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_ATTACH, NULL, 0, NULL) == 0); +} + +/* Called from IO thread */ +void pa_source_detach_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->detach) + o->detach(o); +} + +/* Called from IO thread */ +void pa_source_attach_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->attach) + o->attach(o); +} + +/* Called from IO thread */ +pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { + pa_usec_t result = (pa_usec_t) -1; + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + + if (s->thread_info.requested_latency_valid) + return s->thread_info.requested_latency; + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + + if (o->thread_info.requested_source_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency)) + result = o->thread_info.requested_source_latency; + + if (result != (pa_usec_t) -1) { + if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency) + result = s->thread_info.max_latency; + + if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency) + result = s->thread_info.min_latency; + } + + s->thread_info.requested_latency = result; + s->thread_info.requested_latency_valid = TRUE; + + return result; +} + +/* Called from main thread */ +pa_usec_t pa_source_get_requested_latency(pa_source *s) { + pa_usec_t usec; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (!PA_SOURCE_IS_OPENED(s->state)) + return 0; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + + return usec; +} + +/* Called from IO thread */ +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + + if (max_rewind == s->thread_info.max_rewind) + return; + + s->thread_info.max_rewind = max_rewind; + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + pa_source_output_update_max_rewind(o, s->thread_info.max_rewind); + } +} + +void pa_source_invalidate_requested_latency(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + + s->thread_info.requested_latency_valid = FALSE; + + if (s->update_requested_latency) + s->update_requested_latency(s); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + o->update_source_requested_latency(o); + + if (s->monitor_of) + pa_sink_invalidate_requested_latency(s->monitor_of); +} + +void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_source_assert_ref(s); + + /* min_latency == 0: no limit + * min_latency == (size_t) -1: default limit + * min_latency anything else: specified limit + * + * Similar for max_latency */ + + if (min_latency == (pa_usec_t) -1) + min_latency = DEFAULT_MIN_LATENCY; + + if (max_latency == (pa_usec_t) -1) + max_latency = min_latency; + + pa_assert(!min_latency || !max_latency || + min_latency <= max_latency); + + if (PA_SINK_IS_LINKED(s->state)) { + pa_usec_t r[2]; + + r[0] = min_latency; + r[1] = max_latency; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); + } else { + s->thread_info.min_latency = min_latency; + s->thread_info.max_latency = max_latency; + + s->thread_info.requested_latency_valid = FALSE; + } +} + +void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency) { + pa_source_assert_ref(s); + pa_assert(min_latency); + pa_assert(max_latency); + + if (PA_SOURCE_IS_LINKED(s->state)) { + pa_usec_t r[2] = { 0, 0 }; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY_RANGE, r, 0, NULL) == 0); + + *min_latency = r[0]; + *max_latency = r[1]; + } else { + *min_latency = s->thread_info.min_latency; + *max_latency = s->thread_info.max_latency; + } +} + +/* Called from IO thread */ +void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + + s->thread_info.min_latency = min_latency; + s->thread_info.max_latency = max_latency; + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->update_source_latency_range) + o->update_source_latency_range(o); + + pa_source_invalidate_requested_latency(s); +} + +size_t pa_source_get_max_rewind(pa_source *s) { + size_t r; + pa_source_assert_ref(s); + + if (!PA_SOURCE_IS_LINKED(s->state)) + return s->thread_info.max_rewind; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MAX_REWIND, &r, 0, NULL) == 0); + + return r; +} diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 4dbe4e01..f4a17e8d 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -1,21 +1,22 @@ -#ifndef foosourcehfoo -#define foosourcehfoo - -/* $Id$ */ +#ifndef foopulsesourcehfoo +#define foopulsesourcehfoo /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -29,80 +30,228 @@ typedef struct pa_source pa_source; #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> +#include <pulsecore/source-output.h> -#define PA_MAX_OUTPUTS_PER_SOURCE 16 +#define PA_MAX_OUTPUTS_PER_SOURCE 32 typedef enum pa_source_state { + PA_SOURCE_INIT, PA_SOURCE_RUNNING, - PA_SOURCE_DISCONNECTED + PA_SOURCE_SUSPENDED, + PA_SOURCE_IDLE, + PA_SOURCE_UNLINKED } pa_source_state_t; +static inline pa_bool_t PA_SOURCE_IS_OPENED(pa_source_state_t x) { + return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE; +} + +static inline pa_bool_t PA_SOURCE_IS_LINKED(pa_source_state_t x) { + return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE || x == PA_SOURCE_SUSPENDED; +} + struct pa_source { - int ref; + 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 *owner; /* may be NULL */ + char *driver; /* may be NULL */ + pa_proplist *proplist; + + 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 hw_volume, sw_volume; - int hw_muted, sw_muted; - - int is_hardware; - - void (*notify)(pa_source*source); /* may be NULL */ - pa_usec_t (*get_latency)(pa_source *s); /* dito */ - int (*set_hw_volume)(pa_source *s); /* dito */ - int (*get_hw_volume)(pa_source *s); /* dito */ - int (*set_hw_mute)(pa_source *s); /* dito */ - int (*get_hw_mute)(pa_source *s); /* dito */ - + pa_cvolume volume; + pa_bool_t muted; + + pa_bool_t refresh_volume:1; + pa_bool_t refresh_muted:1; + + pa_asyncmsgq *asyncmsgq; + pa_rtpoll *rtpoll; + + pa_memchunk silence; + + /* Called when the main loop requests a state change. Called from + * main loop context. If returns -1 the state change will be + * inhibited */ + int (*set_state)(pa_source*source, pa_source_state_t state); /* may be NULL */ + + /* Callled when the volume is queried. Called from main loop + * context. If this is NULL a PA_SOURCE_MESSAGE_GET_VOLUME message + * will be sent to the IO thread instead. If refresh_volume is + * FALSE neither this function is called nor a message is sent. */ + int (*get_volume)(pa_source *s); /* dito */ + + /* Called when the volume shall be changed. Called from main loop + * context. If this is NULL a PA_SOURCE_MESSAGE_SET_VOLUME message + * will be sent to the IO thread instead. */ + int (*set_volume)(pa_source *s); /* dito */ + + /* Called when the mute setting is queried. Called from main loop + * context. If this is NULL a PA_SOURCE_MESSAGE_GET_MUTE message + * will be sent to the IO thread instead. If refresh_mute is + * FALSE neither this function is called nor a message is sent.*/ + int (*get_mute)(pa_source *s); /* dito */ + + /* Called when the mute setting shall be changed. Called from main + * loop context. If this is NULL a PA_SOURCE_MESSAGE_SET_MUTE + * message will be sent to the IO thread instead. */ + int (*set_mute)(pa_source *s); /* dito */ + + /* Called when a the requested latency is changed. Called from IO + * thread context. */ + void (*update_requested_latency)(pa_source *s); /* dito */ + + /* 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:1; + + pa_bool_t requested_latency_valid:1; + pa_usec_t requested_latency; + + /* Then number of bytes this source will be rewound for at + * max. (Only used on monitor sources) */ + size_t max_rewind; + + pa_usec_t min_latency; /* we won't go below this latency */ + pa_usec_t max_latency; /* An upper limit for the latencies */ + } 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_GET_REQUESTED_LATENCY, + PA_SOURCE_MESSAGE_SET_STATE, + PA_SOURCE_MESSAGE_ATTACH, + PA_SOURCE_MESSAGE_DETACH, + PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, + PA_SOURCE_MESSAGE_GET_LATENCY_RANGE, + PA_SOURCE_MESSAGE_GET_MAX_REWIND, + PA_SOURCE_MESSAGE_MAX +} pa_source_message_t; + +typedef struct pa_source_new_data { + char *name; + pa_bool_t namereg_fail; + pa_proplist *proplist; + + const char *driver; + pa_module *module; + + 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_source_new_data; + +pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data); +void pa_source_new_data_set_name(pa_source_new_data *data, const char *name); +void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec); +void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map); +void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume); +void pa_source_new_data_set_muted(pa_source_new_data *data, pa_bool_t mute); +void pa_source_new_data_done(pa_source_new_data *data); + +/* To be called exclusively by the source driver, from main context */ + pa_source* pa_source_new( - pa_core *core, - const char *driver, - const char *name, - int namereg_fail, - const pa_sample_spec *spec, - const pa_channel_map *map); + pa_core *core, + pa_source_new_data *data, + pa_source_flags_t flags); + +void pa_source_put(pa_source *s); +void pa_source_unlink(pa_source *s); -void pa_source_disconnect(pa_source *s); -void pa_source_unref(pa_source *s); -pa_source* pa_source_ref(pa_source *c); +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); -/* Pass a new memory block to all output streams */ -void pa_source_post(pa_source*s, const pa_memchunk *b); +void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); -void pa_source_notify(pa_source *s); +void pa_source_detach(pa_source *s); +void pa_source_attach(pa_source *s); -void pa_source_set_owner(pa_source *s, pa_module *m); +/* May be called by everyone, from main context */ +/* The returned value is supposed to be in the time domain of the sound card! */ pa_usec_t pa_source_get_latency(pa_source *s); +pa_usec_t pa_source_get_requested_latency(pa_source *s); +void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency); -void pa_source_set_volume(pa_source *source, pa_mixer_t m, const pa_cvolume *volume); -const pa_cvolume *pa_source_get_volume(pa_source *source, pa_mixer_t m); -void pa_source_set_mute(pa_source *source, pa_mixer_t m, int mute); -int pa_source_get_mute(pa_source *source, pa_mixer_t m); +size_t pa_source_get_max_rewind(pa_source *s); -void pa_source_set_description(pa_source *s, const char *description); +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_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 *chunk); +void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk); +void pa_source_process_rewind(pa_source *s, size_t nbytes); + +int pa_source_process_msg(pa_msgobject *o, int code, void *userdata, int64_t, pa_memchunk *chunk); + +void pa_source_attach_within_thread(pa_source *s); +void pa_source_detach_within_thread(pa_source *s); + +pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s); + +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind); +void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); + +/* To be called exclusively by source output drivers, from IO context */ + +void pa_source_invalidate_requested_latency(pa_source *s); -unsigned pa_source_used_by(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..617e4afb --- /dev/null +++ b/src/pulsecore/speexwrap.h @@ -0,0 +1,48 @@ +#ifndef foopulsespeexwraphfoo +#define foopulsespeexwraphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General 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..1661383d --- /dev/null +++ b/src/pulsecore/start-child.c @@ -0,0 +1,160 @@ +/*** + 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..0b5ff660 --- /dev/null +++ b/src/pulsecore/start-child.h @@ -0,0 +1,30 @@ +#ifndef foopulsestartchildhfoo +#define foopulsestartchildhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2007 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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 index ef8160dc..b59b6f49 100644 --- a/src/pulsecore/strbuf.c +++ b/src/pulsecore/strbuf.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -25,12 +25,12 @@ #include <sys/types.h> #include <stdlib.h> -#include <assert.h> #include <string.h> #include <stdarg.h> #include <stdio.h> #include <pulse/xmalloc.h> +#include <pulsecore/macro.h> #include "strbuf.h" @@ -40,7 +40,7 @@ struct chunk { size_t length; }; -#define CHUNK_TO_TEXT(c) ((char*) (c) + sizeof(struct chunk)) +#define CHUNK_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(struct chunk))) struct pa_strbuf { size_t length; @@ -48,14 +48,18 @@ struct pa_strbuf { }; pa_strbuf *pa_strbuf_new(void) { - pa_strbuf *sb = pa_xmalloc(sizeof(pa_strbuf)); + 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) { - assert(sb); + pa_assert(sb); + while (sb->head) { struct chunk *c = sb->head; sb->head = sb->head->next; @@ -70,12 +74,13 @@ void pa_strbuf_free(pa_strbuf *sb) { char *pa_strbuf_tostring(pa_strbuf *sb) { char *t, *e; struct chunk *c; - assert(sb); - e = t = pa_xmalloc(sb->length+1); + pa_assert(sb); + + e = t = pa_xnew(char, sb->length+1); for (c = sb->head; c; c = c->next) { - assert((size_t) (e-t) <= sb->length); + pa_assert((size_t) (e-t) <= sb->length); memcpy(e, CHUNK_TO_TEXT(c), c->length); e += c->length; } @@ -83,35 +88,41 @@ char *pa_strbuf_tostring(pa_strbuf *sb) { /* Trailing NUL */ *e = 0; - assert(e == t+sb->length); - + 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; - assert(sb); + + 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) { - assert(sb && 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) { - assert(sb && c); + pa_assert(sb); + pa_assert(c); if (sb->tail) { - assert(sb->head); + pa_assert(sb->head); sb->tail->next = c; } else { - assert(!sb->head); + pa_assert(!sb->head); sb->head = c; } @@ -123,12 +134,14 @@ static void append(pa_strbuf *sb, struct chunk *c) { /* 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; - assert(sb && t); - + + pa_assert(sb); + pa_assert(t); + if (!l) return; - - c = pa_xmalloc(sizeof(struct chunk)+l); + + c = pa_xmalloc(PA_ALIGN(sizeof(struct chunk)) + l); c->length = l; memcpy(CHUNK_TO_TEXT(c), t, l); @@ -141,18 +154,20 @@ int pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) { int size = 100; struct chunk *c = NULL; - assert(sb); - + pa_assert(sb); + pa_assert(format); + for(;;) { va_list ap; int r; - c = pa_xrealloc(c, sizeof(struct chunk)+size); + 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); @@ -160,7 +175,7 @@ int pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) { } if (r > -1) /* glibc 2.1 */ - size = r+1; + size = r+1; else /* glibc 2.0 */ size *= 2; } diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h index c45fb15f..24c876d5 100644 --- a/src/pulsecore/strbuf.h +++ b/src/pulsecore/strbuf.h @@ -1,28 +1,28 @@ #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> +#include <pulse/gccmacro.h> typedef struct pa_strbuf pa_strbuf; diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c index df3a0275..f587a2f8 100644 --- a/src/pulsecore/strlist.c +++ b/src/pulsecore/strlist.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,26 +24,31 @@ #endif #include <string.h> -#include <assert.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; - char *str; }; +#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; - assert(s); - n = pa_xmalloc(sizeof(pa_strlist)); - n->str = pa_xstrdup(s); + 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; } @@ -56,7 +61,7 @@ char *pa_strlist_tostring(pa_strlist *l) { if (!first) pa_strbuf_puts(b, " "); first = 0; - pa_strbuf_puts(b, l->str); + pa_strbuf_puts(b, ITEM_TO_TEXT(l)); } return pa_strbuf_tostring_free(b); @@ -64,23 +69,24 @@ char *pa_strlist_tostring(pa_strlist *l) { pa_strlist* pa_strlist_remove(pa_strlist *l, const char *s) { pa_strlist *ret = l, *prev = NULL; - assert(l && s); + + pa_assert(l); + pa_assert(s); while (l) { - if (!strcmp(l->str, s)) { + if (!strcmp(ITEM_TO_TEXT(l), s)) { pa_strlist *n = l->next; - + if (!prev) { - assert(ret == l); + pa_assert(ret == l); ret = n; } else prev->next = n; - pa_xfree(l->str); pa_xfree(l); l = n; - + } else { prev = l; l = l->next; @@ -94,22 +100,21 @@ void pa_strlist_free(pa_strlist *l) { while (l) { pa_strlist *c = l; l = l->next; - - pa_xfree(c->str); pa_xfree(c); } } pa_strlist* pa_strlist_pop(pa_strlist *l, char **s) { pa_strlist *r; - assert(s); - + + pa_assert(s); + if (!l) { *s = NULL; return NULL; } - - *s = l->str; + + *s = pa_xstrdup(ITEM_TO_TEXT(l)); r = l->next; pa_xfree(l); return r; @@ -122,10 +127,12 @@ pa_strlist* pa_strlist_parse(const char *s) { while ((r = pa_split_spaces(s, &state))) { pa_strlist *n; + size_t size = strlen(r); - n = pa_xmalloc(sizeof(pa_strlist)); - n->str = 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; @@ -137,3 +144,18 @@ pa_strlist* pa_strlist_parse(const char *s) { 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 index 87925d5e..1cb7537a 100644 --- a/src/pulsecore/strlist.h +++ b/src/pulsecore/strlist.h @@ -1,21 +1,21 @@ #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 @@ -44,4 +44,7 @@ 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 index 11e85c19..b0ed59ef 100644 --- a/src/pulsecore/tagstruct.c +++ b/src/pulsecore/tagstruct.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 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 @@ -27,43 +27,46 @@ #include <string.h> #include <unistd.h> #include <sys/time.h> -#include <assert.h> #include <stdarg.h> #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif -#include "winsock.h" - #include <pulse/xmalloc.h> +#include <pulsecore/winsock.h> +#include <pulsecore/macro.h> + #include "tagstruct.h" +#define MAX_TAG_SIZE (64*1024) struct pa_tagstruct { uint8_t *data; size_t length, allocated; size_t rindex; - int dynamic; + pa_bool_t dynamic; }; pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length) { pa_tagstruct*t; - assert(!data || (data && length)); - - t = pa_xmalloc(sizeof(pa_tagstruct)); + 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) { - assert(t); + pa_assert(t); + if (t->dynamic) pa_xfree(t->data); pa_xfree(t); @@ -71,7 +74,11 @@ void pa_tagstruct_free(pa_tagstruct*t) { uint8_t* pa_tagstruct_free_data(pa_tagstruct*t, size_t *l) { uint8_t *p; - assert(t && t->dynamic && l); + + pa_assert(t); + pa_assert(t->dynamic); + pa_assert(l); + p = t->data; *l = t->length; pa_xfree(t); @@ -79,8 +86,8 @@ uint8_t* pa_tagstruct_free_data(pa_tagstruct*t, size_t *l) { } static void extend(pa_tagstruct*t, size_t l) { - assert(t); - assert(t->dynamic); + pa_assert(t); + pa_assert(t->dynamic); if (t->length+l <= t->allocated) return; @@ -90,7 +97,8 @@ static void extend(pa_tagstruct*t, size_t l) { void pa_tagstruct_puts(pa_tagstruct*t, const char *s) { size_t l; - assert(t); + pa_assert(t); + if (s) { l = strlen(s)+2; extend(t, l); @@ -105,7 +113,8 @@ void pa_tagstruct_puts(pa_tagstruct*t, const char *s) { } void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i) { - assert(t); + pa_assert(t); + extend(t, 5); t->data[t->length] = PA_TAG_U32; i = htonl(i); @@ -114,7 +123,8 @@ void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i) { } void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c) { - assert(t); + pa_assert(t); + extend(t, 2); t->data[t->length] = PA_TAG_U8; *(t->data+t->length+1) = c; @@ -123,7 +133,10 @@ void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c) { void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss) { uint32_t rate; - assert(t && ss); + + 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; @@ -135,7 +148,9 @@ 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) { uint32_t tmp; - assert(t && p); + + pa_assert(t); + pa_assert(p); extend(t, 5+length); t->data[t->length] = PA_TAG_ARBITRARY; @@ -146,8 +161,9 @@ void pa_tagstruct_put_arbitrary(pa_tagstruct *t, const void *p, size_t length) { t->length += 5+length; } -void pa_tagstruct_put_boolean(pa_tagstruct*t, int b) { - assert(t); +void pa_tagstruct_put_boolean(pa_tagstruct*t, pa_bool_t b) { + pa_assert(t); + extend(t, 1); t->data[t->length] = b ? PA_TAG_BOOLEAN_TRUE : PA_TAG_BOOLEAN_FALSE; t->length += 1; @@ -155,7 +171,8 @@ void pa_tagstruct_put_boolean(pa_tagstruct*t, int b) { void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv) { uint32_t tmp; - assert(t); + pa_assert(t); + extend(t, 9); t->data[t->length] = PA_TAG_TIMEVAL; tmp = htonl(tv->tv_sec); @@ -167,7 +184,9 @@ void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv) { void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u) { uint32_t tmp; - assert(t); + + pa_assert(t); + extend(t, 9); t->data[t->length] = PA_TAG_USEC; tmp = htonl((uint32_t) (u >> 32)); @@ -179,7 +198,9 @@ void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u) { void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t u) { uint32_t tmp; - assert(t); + + pa_assert(t); + extend(t, 9); t->data[t->length] = PA_TAG_U64; tmp = htonl((uint32_t) (u >> 32)); @@ -191,7 +212,9 @@ void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t u) { void pa_tagstruct_puts64(pa_tagstruct*t, int64_t u) { uint32_t tmp; - assert(t); + + pa_assert(t); + extend(t, 9); t->data[t->length] = PA_TAG_S64; tmp = htonl((uint32_t) ((uint64_t) u >> 32)); @@ -203,13 +226,13 @@ void pa_tagstruct_puts64(pa_tagstruct*t, int64_t u) { void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map) { unsigned i; - - assert(t); + + 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]; } @@ -217,13 +240,13 @@ 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) { unsigned i; pa_volume_t vol; - - assert(t); + + 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)); @@ -231,11 +254,39 @@ void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume) { } } +void pa_tagstruct_put_proplist(pa_tagstruct *t, pa_proplist *p) { + void *state = NULL; + pa_assert(t); + pa_assert(p); + + extend(t, 1); + + t->data[t->length++] = PA_TAG_PROPLIST; + + for (;;) { + const char *k; + const void *d; + size_t l; + + if (!(k = pa_proplist_iterate(p, &state))) + break; + + pa_tagstruct_puts(t, k); + pa_assert_se(pa_proplist_get(p, k, &d, &l) >= 0); + pa_tagstruct_putu32(t, (uint32_t) l); + pa_tagstruct_put_arbitrary(t, d, l); + } + + pa_tagstruct_puts(t, NULL); +} + int pa_tagstruct_gets(pa_tagstruct*t, const char **s) { int error = 0; size_t n; char *c; - assert(t && s); + + pa_assert(t); + pa_assert(s); if (t->rindex+1 > t->length) return -1; @@ -245,10 +296,10 @@ int pa_tagstruct_gets(pa_tagstruct*t, const char **s) { *s = NULL; return 0; } - + if (t->rindex+2 > t->length) return -1; - + if (t->data[t->rindex] != PA_TAG_STRING) return -1; @@ -269,7 +320,8 @@ int pa_tagstruct_gets(pa_tagstruct*t, const char **s) { } int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i) { - assert(t && i); + pa_assert(t); + pa_assert(i); if (t->rindex+5 > t->length) return -1; @@ -284,7 +336,8 @@ int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i) { } int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c) { - assert(t && c); + pa_assert(t); + pa_assert(c); if (t->rindex+2 > t->length) return -1; @@ -298,14 +351,15 @@ int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c) { } int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss) { - assert(t && 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); @@ -317,8 +371,10 @@ 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) { uint32_t len; - assert(t && p); - + + pa_assert(t); + pa_assert(p); + if (t->rindex+5+length > t->length) return -1; @@ -335,35 +391,43 @@ int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length) { } int pa_tagstruct_eof(pa_tagstruct*t) { - assert(t); + pa_assert(t); + return t->rindex >= t->length; } const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l) { - assert(t && t->dynamic && 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) { - assert(t && b); +int pa_tagstruct_get_boolean(pa_tagstruct*t, pa_bool_t *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; + *b = TRUE; else if (t->data[t->rindex] == PA_TAG_BOOLEAN_FALSE) - *b = 0; + *b = FALSE; 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; @@ -380,7 +444,9 @@ int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv) { int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u) { uint32_t tmp; - assert(t && u); + + pa_assert(t); + pa_assert(u); if (t->rindex+9 > t->length) return -1; @@ -398,7 +464,9 @@ int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u) { int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *u) { uint32_t tmp; - assert(t && u); + + pa_assert(t); + pa_assert(u); if (t->rindex+9 > t->length) return -1; @@ -416,7 +484,9 @@ int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *u) { int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *u) { uint32_t tmp; - assert(t && u); + + pa_assert(t); + pa_assert(u); if (t->rindex+9 > t->length) return -1; @@ -434,9 +504,9 @@ int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *u) { int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map) { unsigned i; - - assert(t); - assert(map); + + pa_assert(t); + pa_assert(map); if (t->rindex+2 > t->length) return -1; @@ -449,7 +519,7 @@ int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map) { 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]; @@ -460,9 +530,9 @@ int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map) { int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) { unsigned i; pa_volume_t vol; - - assert(t); - assert(cvolume); + + pa_assert(t); + pa_assert(cvolume); if (t->rindex+2 > t->length) return -1; @@ -475,7 +545,7 @@ int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) { 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); @@ -485,10 +555,56 @@ int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) { return 0; } +int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p) { + size_t saved_rindex; + + pa_assert(t); + pa_assert(p); + + if (t->rindex+1 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_PROPLIST) + return -1; + + saved_rindex = t->rindex; + t->rindex++; + + for (;;) { + const char *k; + const void *d; + uint32_t length; + + if (pa_tagstruct_gets(t, &k) < 0) + goto fail; + + if (!k) + break; + + if (pa_tagstruct_getu32(t, &length) < 0) + goto fail; + + if (length > MAX_TAG_SIZE) + goto fail; + + if (pa_tagstruct_get_arbitrary(t, &d, length) < 0) + goto fail; + + if (pa_proplist_set(p, k, d, length) < 0) + goto fail; + } + + return 0; + +fail: + t->rindex = saved_rindex; + return -1; +} + void pa_tagstruct_put(pa_tagstruct *t, ...) { va_list va; - assert(t); - + pa_assert(t); + va_start(va, t); for (;;) { @@ -547,20 +663,24 @@ void pa_tagstruct_put(pa_tagstruct *t, ...) { pa_tagstruct_put_cvolume(t, va_arg(va, pa_cvolume *)); break; + case PA_TAG_PROPLIST: + pa_tagstruct_put_proplist(t, va_arg(va, pa_proplist *)); + break; + default: - abort(); + pa_assert_not_reached(); } } - + va_end(va); } int pa_tagstruct_get(pa_tagstruct *t, ...) { va_list va; int ret = 0; - - assert(t); - + + pa_assert(t); + va_start(va, t); while (ret == 0) { int tag = va_arg(va, int); @@ -599,7 +719,7 @@ int pa_tagstruct_get(pa_tagstruct *t, ...) { case PA_TAG_BOOLEAN_TRUE: case PA_TAG_BOOLEAN_FALSE: - ret = pa_tagstruct_get_boolean(t, va_arg(va, int*)); + ret = pa_tagstruct_get_boolean(t, va_arg(va, pa_bool_t*)); break; case PA_TAG_TIMEVAL: @@ -618,11 +738,14 @@ int pa_tagstruct_get(pa_tagstruct *t, ...) { ret = pa_tagstruct_get_cvolume(t, va_arg(va, pa_cvolume *)); break; - + case PA_TAG_PROPLIST: + ret = pa_tagstruct_get_proplist(t, va_arg(va, pa_proplist *)); + break; + default: - abort(); + pa_assert_not_reached(); } - + } va_end(va); diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h index 4c56f328..e7d07054 100644 --- a/src/pulsecore/tagstruct.h +++ b/src/pulsecore/tagstruct.h @@ -1,21 +1,21 @@ #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 @@ -30,6 +30,10 @@ #include <pulse/sample.h> #include <pulse/channelmap.h> #include <pulse/volume.h> +#include <pulse/proplist.h> +#include <pulse/gccmacro.h> + +#include <pulsecore/macro.h> typedef struct pa_tagstruct pa_tagstruct; @@ -49,7 +53,8 @@ enum { PA_TAG_TIMEVAL = 'T', PA_TAG_USEC = 'U' /* 64bit unsigned */, PA_TAG_CHANNEL_MAP = 'm', - PA_TAG_CVOLUME = 'v' + PA_TAG_CVOLUME = 'v', + PA_TAG_PROPLIST = 'P' }; pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length); @@ -68,11 +73,12 @@ 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_boolean(pa_tagstruct*t, pa_bool_t b); void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv); void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u); void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map); void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume); +void pa_tagstruct_put_proplist(pa_tagstruct *t, pa_proplist *p); int pa_tagstruct_get(pa_tagstruct *t, ...); @@ -83,11 +89,12 @@ 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_boolean(pa_tagstruct *t, pa_bool_t *b); int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv); int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u); int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map); int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *v); +int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p); #endif diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c new file mode 100644 index 00000000..34f92a7e --- /dev/null +++ b/src/pulsecore/thread-mq.c @@ -0,0 +1,127 @@ +/*** + 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_read_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_thread_mq *q = userdata; + pa_asyncmsgq *aq; + + pa_assert(pa_asyncmsgq_read_fd(q->outq) == fd); + pa_assert(events == PA_IO_EVENT_INPUT); + + pa_asyncmsgq_ref(aq = q->outq); + pa_asyncmsgq_write_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_read_before_poll(aq) == 0) + break; + } + + pa_asyncmsgq_unref(aq); +} + +static void asyncmsgq_write_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_thread_mq *q = userdata; + + pa_assert(pa_asyncmsgq_write_fd(q->inq) == fd); + pa_assert(events == PA_IO_EVENT_INPUT); + + pa_asyncmsgq_write_after_poll(q->inq); + pa_asyncmsgq_write_before_poll(q->inq); +} + +void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll) { + 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_read_before_poll(q->outq) == 0); + pa_assert_se(q->read_event = mainloop->io_new(mainloop, pa_asyncmsgq_read_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q)); + + pa_asyncmsgq_write_before_poll(q->inq); + pa_assert_se(q->write_event = mainloop->io_new(mainloop, pa_asyncmsgq_write_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_write_cb, q)); + + pa_rtpoll_item_new_asyncmsgq_read(rtpoll, PA_RTPOLL_EARLY, q->inq); + pa_rtpoll_item_new_asyncmsgq_write(rtpoll, PA_RTPOLL_LATE, q->outq); +} + +void pa_thread_mq_done(pa_thread_mq *q) { + pa_assert(q); + + q->mainloop->io_free(q->read_event); + q->mainloop->io_free(q->write_event); + q->read_event = q->write_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..3b5e0e78 --- /dev/null +++ b/src/pulsecore/thread-mq.h @@ -0,0 +1,48 @@ +#ifndef foopulsethreadmqhfoo +#define foopulsethreadmqhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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> +#include <pulsecore/rtpoll.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 *read_event, *write_event; +} pa_thread_mq; + +void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll); +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 index d69790a5..20ed16d9 100644 --- a/src/pulsecore/thread-posix.c +++ b/src/pulsecore/thread-posix.c @@ -1,18 +1,19 @@ -/* $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 @@ -23,7 +24,6 @@ #include <config.h> #endif -#include <assert.h> #include <pthread.h> #include <sched.h> #include <errno.h> @@ -32,65 +32,53 @@ #include <pulsecore/mutex.h> #include <pulsecore/once.h> #include <pulsecore/atomic.h> +#include <pulsecore/macro.h> #include "thread.h" -#define ASSERT_SUCCESS(x) do { \ - int _r = (x); \ - assert(_r == 0); \ -} while(0) - struct pa_thread { pthread_t id; pa_thread_func_t thread_func; void *userdata; - pa_atomic_int_t running; + pa_atomic_t running; }; struct pa_tls { pthread_key_t key; }; -static pa_tls *thread_tls; -static pa_once_t thread_tls_once = PA_ONCE_INIT; - -static void tls_free_cb(void *p) { +static void thread_free_cb(void *p) { pa_thread *t = p; - assert(t); - - if (!t->thread_func) + pa_assert(t); + + if (!t->thread_func) /* This is a foreign thread, we need to free the struct */ pa_xfree(t); } -static void thread_tls_once_func(void) { - thread_tls = pa_tls_new(tls_free_cb); - assert(thread_tls); -} +PA_STATIC_TLS_DECLARE(current_thread, thread_free_cb); static void* internal_thread_func(void *userdata) { pa_thread *t = userdata; - assert(t); + pa_assert(t); t->id = pthread_self(); - pa_once(&thread_tls_once, thread_tls_once_func); - - pa_tls_set(thread_tls, t); - + PA_STATIC_TLS_SET(current_thread, t); + pa_atomic_inc(&t->running); t->thread_func(t->userdata); - pa_atomic_add(&t->running, -2); - + pa_atomic_sub(&t->running, 2); + return NULL; } pa_thread* pa_thread_new(pa_thread_func_t thread_func, void *userdata) { pa_thread *t; - assert(thread_func); - + pa_assert(thread_func); + t = pa_xnew(pa_thread, 1); t->thread_func = thread_func; t->userdata = userdata; @@ -107,65 +95,58 @@ pa_thread* pa_thread_new(pa_thread_func_t thread_func, void *userdata) { } int pa_thread_is_running(pa_thread *t) { - assert(t); - - if (!t->thread_func) { - /* Mhmm, this is a foreign thread, t->running is not - * necessarily valid. We misuse pthread_getschedparam() to - * check if the thread is valid. This might not be portable. */ + pa_assert(t); - int policy; - struct sched_param param; - - return pthread_getschedparam(t->id, &policy, ¶m) >= 0 || errno != ESRCH; - } + /* 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) { - assert(t); - + pa_assert(t); + pa_thread_join(t); pa_xfree(t); } int pa_thread_join(pa_thread *t) { - assert(t); - + pa_assert(t); + return pthread_join(t->id, NULL); } pa_thread* pa_thread_self(void) { pa_thread *t; - - pa_once(&thread_tls_once, thread_tls_once_func); - if ((t = pa_tls_get(thread_tls))) + 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_tls_set(thread_tls, t); - + PA_STATIC_TLS_SET(current_thread, t); + return t; } void* pa_thread_get_data(pa_thread *t) { - assert(t); + pa_assert(t); return t->userdata; } void pa_thread_set_data(pa_thread *t, void *userdata) { - assert(t); + pa_assert(t); t->userdata = userdata; } @@ -174,7 +155,7 @@ void pa_thread_yield(void) { #ifdef HAVE_PTHREAD_YIELD pthread_yield(); #else - ASSERT_SUCCESS(sched_yield()); + pa_assert_se(sched_yield() == 0); #endif } @@ -187,28 +168,28 @@ pa_tls* pa_tls_new(pa_free_cb_t free_cb) { pa_xfree(t); return NULL; } - + return t; } void pa_tls_free(pa_tls *t) { - assert(t); + pa_assert(t); - ASSERT_SUCCESS(pthread_key_delete(t->key)); + pa_assert_se(pthread_key_delete(t->key) == 0); pa_xfree(t); } void *pa_tls_get(pa_tls *t) { - assert(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); - ASSERT_SUCCESS(pthread_setspecific(t->key, userdata)); + 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 index 98ea0691..c40d3342 100644 --- a/src/pulsecore/thread-win32.c +++ b/src/pulsecore/thread-win32.c @@ -1,18 +1,18 @@ -/* $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 @@ -51,9 +51,8 @@ struct pa_tls_monitor { }; static pa_tls *thread_tls; -static pa_once_t thread_tls_once = PA_ONCE_INIT; +static pa_once thread_tls_once = PA_ONCE_INIT; static pa_tls *monitor_tls; -static pa_once_t monitor_tls_once = PA_ONCE_INIT; static void thread_tls_once_func(void) { thread_tls = pa_tls_new(NULL); @@ -64,7 +63,7 @@ static DWORD WINAPI internal_thread_func(LPVOID param) { pa_thread *t = param; assert(t); - pa_once(&thread_tls_once, thread_tls_once_func); + pa_run_once(&thread_tls_once, thread_tls_once_func); pa_tls_set(thread_tls, t); t->thread_func(t->userdata); @@ -120,7 +119,7 @@ int pa_thread_join(pa_thread *t) { } pa_thread* pa_thread_self(void) { - pa_once(&thread_tls_once, thread_tls_once_func); + pa_run_once(&thread_tls_once, thread_tls_once_func); return pa_tls_get(thread_tls); } @@ -128,12 +127,6 @@ void pa_thread_yield(void) { Sleep(0); } -static void monitor_tls_once_func(void) { - monitor_tls = pa_tls_new(NULL); - assert(monitor_tls); - pa_tls_set(monitor_tls, NULL); -} - static DWORD WINAPI monitor_thread_func(LPVOID param) { struct pa_tls_monitor *m = param; assert(m); @@ -189,7 +182,11 @@ void *pa_tls_set(pa_tls *t, void *userdata) { if (t->free_func) { struct pa_tls_monitor *m; - pa_once(&monitor_tls_once, monitor_tls_once_func); + 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) { diff --git a/src/pulsecore/thread.h b/src/pulsecore/thread.h index d08990a2..f3aca13e 100644 --- a/src/pulsecore/thread.h +++ b/src/pulsecore/thread.h @@ -1,21 +1,22 @@ #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 @@ -23,6 +24,11 @@ ***/ #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; @@ -45,4 +51,60 @@ 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..fe5a4f18 --- /dev/null +++ b/src/pulsecore/time-smoother.c @@ -0,0 +1,459 @@ +/*** + 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 64 + +/* + * Implementation of a time smoothing algorithm to synchronize remote + * clocks to a local one. Evens out noise, adjusts to clock skew and + * allows cheap estimations of the remote time while clock updates may + * be seldom and 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_usec_t time_offset; + + pa_usec_t px, py; /* Point p, where we want to reach stability */ + double dp; /* Gradient we want at point p */ + + pa_usec_t ex, ey; /* Point e, which we estimated before and need to smooth to */ + double de; /* Gradient we estimated for point e */ + pa_usec_t ry; /* The original y value for ex */ + + /* History of last measurements */ + pa_usec_t history_x[HISTORY_MAX], history_y[HISTORY_MAX]; + unsigned history_idx, n_history; + + /* To even out for monotonicity */ + pa_usec_t last_y, last_x; + + /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */ + double a, b, c; + pa_bool_t abc_valid; + + pa_bool_t monotonic:1; + pa_bool_t paused:1; + + pa_usec_t pause_time; + + unsigned min_history; +}; + +pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic, unsigned min_history) { + pa_smoother *s; + + pa_assert(adjust_time > 0); + pa_assert(history_time > 0); + pa_assert(min_history >= 2); + pa_assert(min_history <= HISTORY_MAX); + + s = pa_xnew(pa_smoother, 1); + s->adjust_time = adjust_time; + s->history_time = history_time; + s->time_offset = 0; + s->monotonic = monotonic; + + s->px = s->py = 0; + s->dp = 1; + + s->ex = s->ey = s->ry = 0; + s->de = 1; + + s->history_idx = 0; + s->n_history = 0; + + s->last_y = s->last_x = 0; + + s->abc_valid = FALSE; + + s->paused = FALSE; + + s->min_history = min_history; + + return s; +} + +void pa_smoother_free(pa_smoother* s) { + pa_assert(s); + + pa_xfree(s); +} + +#define REDUCE(x) \ + do { \ + x = (x) % HISTORY_MAX; \ + } while(FALSE) + +#define REDUCE_INC(x) \ + do { \ + x = ((x)+1) % HISTORY_MAX; \ + } while(FALSE) + + +static void drop_old(pa_smoother *s, pa_usec_t x) { + + /* Drop items from history which are too old, but make sure to + * always keep min_history in the history */ + + while (s->n_history > s->min_history) { + + if (s->history_x[s->history_idx] + s->history_time >= x) + /* This item is still valid, and thus all following ones + * are too, so let's quit this loop */ + break; + + /* Item is too old, let's drop it */ + REDUCE_INC(s->history_idx); + + s->n_history --; + } +} + +static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) { + unsigned j, i; + pa_assert(s); + + /* First try to update an existing history entry */ + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + + if (s->history_x[i] == x) { + s->history_y[i] = y; + return; + } + + REDUCE_INC(i); + } + + /* Drop old entries */ + drop_old(s, x); + + /* Calculate position for new entry */ + j = s->history_idx + s->n_history; + REDUCE(j); + + /* Fill in entry */ + s->history_x[j] = x; + s->history_y[j] = y; + + /* Adjust counter */ + s->n_history ++; + + /* And make sure we don't store more entries than fit in */ + if (s->n_history > HISTORY_MAX) { + s->history_idx += s->n_history - HISTORY_MAX; + REDUCE(s->history_idx); + s->n_history = HISTORY_MAX; + } +} + +static double avg_gradient(pa_smoother *s, pa_usec_t x) { + unsigned i, j, c = 0; + int64_t ax = 0, ay = 0, k, t; + double r; + + /* Too few measurements, assume gradient of 1 */ + if (s->n_history < s->min_history) + return 1; + + /* First, calculate average of all measurements */ + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + + ax += s->history_x[i]; + ay += s->history_y[i]; + c++; + + REDUCE_INC(i); + } + + pa_assert(c >= s->min_history); + ax /= c; + ay /= c; + + /* Now, do linear regression */ + k = t = 0; + + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + int64_t dx, dy; + + dx = (int64_t) s->history_x[i] - ax; + dy = (int64_t) s->history_y[i] - ay; + + k += dx*dy; + t += dx*dx; + + REDUCE_INC(i); + } + + r = (double) k / t; + + return (s->monotonic && r < 0) ? 0 : r; +} + +static void calc_abc(pa_smoother *s) { + pa_usec_t ex, ey, px, py; + int64_t kx, ky; + double de, dp; + + pa_assert(s); + + if (s->abc_valid) + return; + + /* We have two points: (ex|ey) and (px|py) with two gradients at + * these points de and dp. We do a polynomial + * interpolation of degree 3 with these 6 values */ + + ex = s->ex; ey = s->ey; + px = s->px; py = s->py; + de = s->de; dp = s->dp; + + pa_assert(ex < px); + + /* To increase the dynamic range and 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+bx^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; +} + +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 { + + /* Ok, we're not yet on track, thus let's interpolate, and + * make sure that the first derivative is smooth */ + + calc_abc(s); + + /* Move to origin */ + 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 (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_bool_t is_new; + + pa_assert(s); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0; + + is_new = x >= s->ex; + + if (is_new) { + /* First, we calculate the position we'd estimate for x, so that + * we can adjust our position smoothly from this one */ + estimate(s, x, &ney, &nde); + s->ex = x; s->ey = ney; s->de = nde; + + s->ry = y; + } + + /* Then, we add the new measurement to our history */ + add_to_history(s, x, y); + + /* And determine the average gradient of the history */ + s->dp = avg_gradient(s, x); + + /* And calculate when we want to be on track again */ + s->px = s->ex + s->adjust_time; + s->py = s->ry + s->dp *s->adjust_time; + + s->abc_valid = FALSE; + +/* pa_log_debug("put(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ +} + +pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { + pa_usec_t y; + + pa_assert(s); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0; + + estimate(s, x, &y, NULL); + + if (s->monotonic) { + + /* Make sure the querier doesn't jump forth and back. */ + pa_assert(x >= s->last_x); + s->last_x = x; + + if (y < s->last_y) + y = s->last_y; + else + s->last_y = y; + } + +/* pa_log_debug("get(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ + + return y; +} + +void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) { + pa_assert(s); + + s->time_offset = offset; + +/* pa_log_debug("offset(%llu)", (unsigned long long) offset); */ +} + +void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { + pa_assert(s); + + if (s->paused) + return; + +/* pa_log_debug("pause(%llu)", (unsigned long long) x); */ + + 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); + +/* pa_log_debug("resume(%llu)", (unsigned long long) x); */ + + s->paused = FALSE; + s->time_offset += x - s->pause_time; +} + +pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) { + pa_usec_t ney; + double nde; + + pa_assert(s); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0; + + estimate(s, x, &ney, &nde); + + /* Play safe and take the larger gradient, so that we wakeup + * earlier when this is used for sleeping */ + if (s->dp > nde) + nde = s->dp; + +/* pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); */ + + return (pa_usec_t) ((double) y_delay / nde); +} + +void pa_smoother_reset(pa_smoother *s) { + pa_assert(s); + + s->n_history = 0; + s->abc_valid = FALSE; + +} diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h new file mode 100644 index 00000000..2051e640 --- /dev/null +++ b/src/pulsecore/time-smoother.h @@ -0,0 +1,49 @@ +#ifndef foopulsetimesmootherhfoo +#define foopulsetimesmootherhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2007 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, 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 x_adjust_time, pa_usec_t x_history_time, pa_bool_t monotonic, unsigned min_history); +void pa_smoother_free(pa_smoother* s); + +/* Adds a new value to our dataset. x = local/system time, y = remote time */ +void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y); + +/* Returns an interpolated value based on the dataset. x = local/system time, return value = remote time */ +pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x); + +/* Translates a time span from the remote time domain to the local one. x = local/system time when to estimate, y_delay = remote time span */ +pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay); + +void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t x_offset); + +void pa_smoother_pause(pa_smoother *s, pa_usec_t x); +void pa_smoother_resume(pa_smoother *s, pa_usec_t x); + +void pa_smoother_reset(pa_smoother *s); + +#endif diff --git a/src/pulsecore/tokenizer.c b/src/pulsecore/tokenizer.c index e799c1e6..d1e0836b 100644 --- a/src/pulsecore/tokenizer.c +++ b/src/pulsecore/tokenizer.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -24,20 +24,16 @@ #endif #include <string.h> -#include <assert.h> #include <stdlib.h> #include <pulse/xmalloc.h> +#include <pulse/gccmacro.h> #include <pulsecore/dynarray.h> -#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> #include "tokenizer.h" -struct pa_tokenizer { - pa_dynarray *dynarray; -}; - static void token_free(void *p, PA_GCC_UNUSED void *userdata) { pa_xfree(p); } @@ -46,7 +42,9 @@ static void parse(pa_dynarray*a, const char *s, unsigned args) { int infty = 0; const char delimiter[] = " \t\n\r"; const char *p; - assert(a && s); + + pa_assert(a); + pa_assert(s); if (args == 0) infty = 1; @@ -68,23 +66,23 @@ static void parse(pa_dynarray*a, const char *s, unsigned args) { } pa_tokenizer* pa_tokenizer_new(const char *s, unsigned args) { - pa_tokenizer *t; - - t = pa_xmalloc(sizeof(pa_tokenizer)); - t->dynarray = pa_dynarray_new(); - assert(t->dynarray); - - parse(t->dynarray, s, args); - return t; + pa_dynarray *a; + + a = pa_dynarray_new(); + parse(a, s, args); + return (pa_tokenizer*) a; } void pa_tokenizer_free(pa_tokenizer *t) { - assert(t); - pa_dynarray_free(t->dynarray, token_free, NULL); - pa_xfree(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) { - assert(t); - return pa_dynarray_get(t->dynarray, 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 index b9a5c55b..d51cd73e 100644 --- a/src/pulsecore/tokenizer.h +++ b/src/pulsecore/tokenizer.h @@ -1,21 +1,21 @@ #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 diff --git a/src/pulsecore/winsock.h b/src/pulsecore/winsock.h index ae868b38..0352bf4d 100644 --- a/src/pulsecore/winsock.h +++ b/src/pulsecore/winsock.h @@ -15,6 +15,8 @@ #define EHOSTUNREACH WSAEHOSTUNREACH #define EWOULDBLOCK WSAEWOULDBLOCK +typedef long suseconds_t; + #endif #ifdef HAVE_WS2TCPIP_H diff --git a/src/pulsecore/x11prop.c b/src/pulsecore/x11prop.c index dd4ff99e..9e75f63a 100644 --- a/src/pulsecore/x11prop.c +++ b/src/pulsecore/x11prop.c @@ -1,8 +1,8 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, @@ -30,7 +30,6 @@ #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); @@ -48,7 +47,7 @@ char* pa_x11_get_prop(Display *d, const char *name, char *p, size_t l) { 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; @@ -65,6 +64,6 @@ finish: if (prop) XFree(prop); - + return ret; } diff --git a/src/pulsecore/x11prop.h b/src/pulsecore/x11prop.h index bd24951a..c5998d3e 100644 --- a/src/pulsecore/x11prop.h +++ b/src/pulsecore/x11prop.h @@ -1,11 +1,11 @@ #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, diff --git a/src/pulsecore/x11wrap.c b/src/pulsecore/x11wrap.c index cc993e78..00b6a157 100644 --- a/src/pulsecore/x11wrap.c +++ b/src/pulsecore/x11wrap.c @@ -1,25 +1,28 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General 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 <assert.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #include <stdio.h> #include <pulse/xmalloc.h> @@ -27,6 +30,8 @@ #include <pulsecore/llist.h> #include <pulsecore/log.h> #include <pulsecore/props.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> #include "x11wrap.h" @@ -40,9 +45,9 @@ struct pa_x11_internal { }; struct pa_x11_wrapper { + PA_REFCNT_DECLARE; pa_core *core; - int ref; - + char *property_name; Display *display; @@ -56,48 +61,71 @@ struct pa_x11_wrapper { struct pa_x11_client { PA_LLIST_FIELDS(pa_x11_client); pa_x11_wrapper *wrapper; - int (*callback)(pa_x11_wrapper *w, XEvent *e, void *userdata); + pa_x11_event_cb_t event_cb; + pa_x11_kill_cb_t kill_cb; void *userdata; }; /* Dispatch all pending X11 events */ static void work(pa_x11_wrapper *w) { - assert(w && w->ref >= 1); - + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + pa_x11_wrapper_ref(w); + while (XPending(w->display)) { - pa_x11_client *c; + pa_x11_client *c, *n; XEvent e; XNextEvent(w->display, &e); - for (c = w->clients; c; c = c->next) { - assert(c->callback); - if (c->callback(w, &e, c->userdata) != 0) - break; + for (c = w->clients; c; c = n) { + n = c->next; + + if (c->event_cb) + if (c->event_cb(w, &e, c->userdata) != 0) + break; } } + + pa_x11_wrapper_unref(w); } /* IO notification event for the X11 display connection */ static void display_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) { pa_x11_wrapper *w = userdata; - assert(m && e && fd >= 0 && w && w->ref >= 1); + + 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; - assert(m && e && w && w->ref >= 1); + + 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; - assert(m && e && fd >= 0 && w && w->ref >= 1); + + pa_assert(m); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); XProcessInternalConnection(w->display, fd); @@ -107,10 +135,9 @@ static void internal_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, PA_GCC /* 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; - assert(fd >= 0); + pa_assert(fd >= 0); - i = pa_xmalloc(sizeof(pa_x11_internal)); - assert(i); + 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; @@ -121,7 +148,7 @@ static pa_x11_internal* x11_internal_add(pa_x11_wrapper *w, int fd) { /* Remove an IO source for an X11 internal connection */ static void x11_internal_remove(pa_x11_wrapper *w, pa_x11_internal *i) { - assert(i); + pa_assert(i); PA_LLIST_REMOVE(pa_x11_internal, w->internals, i); w->core->mainloop->io_free(i->io_event); @@ -131,7 +158,10 @@ static void x11_internal_remove(pa_x11_wrapper *w, pa_x11_internal *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; - assert(display && w && fd >= 0); + + pa_assert(display); + pa_assert(w); + pa_assert(fd >= 0); if (opening) *watch_data = (XPointer) x11_internal_add(w, fd); @@ -142,19 +172,18 @@ static void x11_watch(Display *display, XPointer userdata, int fd, Bool opening, static pa_x11_wrapper* x11_wrapper_new(pa_core *c, const char *name, const char *t) { pa_x11_wrapper*w; Display *d; - int r; if (!(d = XOpenDisplay(name))) { pa_log("XOpenDisplay() failed"); return NULL; } - w = pa_xmalloc(sizeof(pa_x11_wrapper)); + w = pa_xnew(pa_x11_wrapper, 1); + PA_REFCNT_INIT(w); w->core = c; - w->ref = 1; 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); @@ -162,31 +191,28 @@ static pa_x11_wrapper* x11_wrapper_new(pa_core *c, const char *name, const char 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); - - r = pa_property_set(c, w->property_name, w); - assert(r >= 0); - + + pa_assert_se(pa_property_set(c, w->property_name, w) >= 0); + return w; } static void x11_wrapper_free(pa_x11_wrapper*w) { - int r; - assert(w); + pa_assert(w); - r = pa_property_remove(w->core, w->property_name); - assert(r >= 0); + pa_assert_se(pa_property_remove(w->core, w->property_name) >= 0); - assert(!w->clients); + 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); } @@ -194,9 +220,10 @@ static void x11_wrapper_free(pa_x11_wrapper*w) { pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name) { char t[256]; pa_x11_wrapper *w; - assert(c); - - snprintf(t, sizeof(t), "x11-wrapper%s%s", name ? "-" : "", name ? name : ""); + + 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); @@ -204,34 +231,58 @@ pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name) { } pa_x11_wrapper* pa_x11_wrapper_ref(pa_x11_wrapper *w) { - assert(w && w->ref >= 1); - w->ref++; + 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) { - assert(w && w->ref >= 1); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); - if (!(--w->ref)) + if (PA_REFCNT_DEC(w) <= 0) x11_wrapper_free(w); } Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w) { - assert(w && w->ref >= 1); + 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) { +void pa_x11_wrapper_kill(pa_x11_wrapper *w) { + pa_x11_client *c, *n; + + pa_assert(w); + + pa_x11_wrapper_ref(w); + + for (c = w->clients; c; c = n) { + n = c->next; + + if (c->kill_cb) + c->kill_cb(w, c->userdata); + } + + pa_x11_wrapper_unref(w); +} + +pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, pa_x11_event_cb_t event_cb, pa_x11_kill_cb_t kill_cb, void *userdata) { pa_x11_client *c; - assert(w && w->ref >= 1); - c = pa_xmalloc(sizeof(pa_x11_client)); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + c = pa_xnew(pa_x11_client, 1); c->wrapper = w; - c->callback = cb; + c->event_cb = event_cb; + c->kill_cb = kill_cb; c->userdata = userdata; PA_LLIST_PREPEND(pa_x11_client, w->clients, c); @@ -240,7 +291,9 @@ pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, int (*cb)(pa_x11_wrapper *w, } void pa_x11_client_free(pa_x11_client *c) { - assert(c && c->wrapper && c->wrapper->ref >= 1); + 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 index fcdd9f6c..badc3a1f 100644 --- a/src/pulsecore/x11wrap.h +++ b/src/pulsecore/x11wrap.h @@ -1,21 +1,21 @@ #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 @@ -28,6 +28,11 @@ typedef struct pa_x11_wrapper pa_x11_wrapper; +typedef struct pa_x11_client pa_x11_client; + +typedef int (*pa_x11_event_cb_t)(pa_x11_wrapper *w, XEvent *e, void *userdata); +typedef void (*pa_x11_kill_cb_t)(pa_x11_wrapper *w, void *userdata); + /* Return the X11 wrapper for this core. In case no wrapper was existant before, allocate a new one */ pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name); @@ -41,10 +46,11 @@ 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; +/* Kill the connection to the X11 display */ +void pa_x11_wrapper_kill(pa_x11_wrapper *w); /* Register an X11 client, that is called for each X11 event */ -pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, int (*cb)(pa_x11_wrapper *w, XEvent *e, void *userdata), void *userdata); +pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, pa_x11_event_cb_t event_cb, pa_x11_kill_cb_t kill_cb, void *userdata); /* Free an X11 client object */ void pa_x11_client_free(pa_x11_client *c); diff --git a/src/tests/asyncmsgq-test.c b/src/tests/asyncmsgq-test.c new file mode 100644 index 00000000..08ad3dd4 --- /dev/null +++ b/src/tests/asyncmsgq-test.c @@ -0,0 +1,108 @@ +/*** + 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..4e8a1207 --- /dev/null +++ b/src/tests/asyncq-test.c @@ -0,0 +1,85 @@ +/*** + 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), TRUE); + 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, TRUE); + + 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 index 124ce576..9c234602 100644 --- a/src/tests/channelmap-test.c +++ b/src/tests/channelmap-test.c @@ -1,33 +1,34 @@ -/* $Id$ */ - #include <stdio.h> #include <assert.h> #include <pulse/channelmap.h> -#include <pulsecore/gccmacro.h> +#include <pulse/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_init_extend(&map, 14, 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/close-test.c b/src/tests/close-test.c new file mode 100644 index 00000000..7a6fec57 --- /dev/null +++ b/src/tests/close-test.c @@ -0,0 +1,20 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <fcntl.h> + +#include <pulsecore/core-util.h> + +int main(int argc, char *argv[]) { + + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDONLY); + + pa_close_all(5, -1); + + return 0; +} diff --git a/src/tests/cpulimit-test.c b/src/tests/cpulimit-test.c index 2302a26d..b7145e8a 100644 --- a/src/tests/cpulimit-test.c +++ b/src/tests/cpulimit-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -30,7 +28,7 @@ #include <signal.h> #include <pulse/mainloop.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> #ifdef TEST2 #include <pulse/mainloop-signal.h> @@ -47,7 +45,7 @@ static time_t start; 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"); @@ -59,7 +57,7 @@ static void func(pa_mainloop_api *m, PA_GCC_UNUSED pa_signal_event *e, PA_GCC_UN int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { pa_mainloop *m; - + m = pa_mainloop_new(); assert(m); @@ -77,7 +75,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { for (;;) { time_t now; time(&now); - + if ((now - start) >= 30) { fprintf(stderr, "Test failed\n"); break; @@ -86,7 +84,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { #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..9f914553 --- /dev/null +++ b/src/tests/envelope-test.c @@ -0,0 +1,246 @@ +/*** + 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 index 06d68311..b2c648da 100644 --- a/src/tests/flist-test.c +++ b/src/tests/flist-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -41,7 +39,7 @@ static int quit = 0; static void spin(void) { int k; - + /* Spin a little */ k = rand() % 10000; for (; k > 0; k--) @@ -100,6 +98,6 @@ int main(int argc, char* argv[]) { 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 index 0cea2b6d..7c7a8996 100644 --- a/src/tests/get-binary-name-test.c +++ b/src/tests/get-binary-name-test.c @@ -1,18 +1,16 @@ -/* $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 diff --git a/src/tests/hook-list-test.c b/src/tests/hook-list-test.c index d68d1b7d..60b965cd 100644 --- a/src/tests/hook-list-test.c +++ b/src/tests/hook-list-test.c @@ -1,14 +1,16 @@ -/* $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) { +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) { +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; } @@ -19,10 +21,10 @@ int main(int argc, char *argv[]) { 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_connect(&hook, PA_HOOK_LATE, (pa_hook_cb_t) func1, (void*) "slot1"); + slot = pa_hook_connect(&hook, PA_HOOK_NORMAL, (pa_hook_cb_t) func2, (void*) "slot2"); + pa_hook_connect(&hook, PA_HOOK_NORMAL, (pa_hook_cb_t) func1, (void*) "slot3"); + pa_hook_fire(&hook, (void*) "call1"); pa_hook_slot_free(slot); @@ -30,6 +32,6 @@ int main(int argc, char *argv[]) { 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 index 54bdd775..9d930774 100644 --- a/src/tests/interpol-test.c +++ b/src/tests/interpol-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -42,10 +40,9 @@ 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) { - +static void stream_write_cb(pa_stream *p, size_t nbytes, void *userdata) { /* Just some silence */ - pa_stream_write(p, pa_xmalloc0(length), length, pa_xfree, 0, PA_SEEK_RELATIVE); + pa_stream_write(p, pa_xmalloc0(nbytes), nbytes, pa_xfree, 0, PA_SEEK_RELATIVE); } /* This is called whenever the context status changes */ @@ -57,26 +54,26 @@ static void context_state_callback(pa_context *c, void *userdata) { 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 + .channels = 2 }; - + 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; @@ -108,36 +105,41 @@ int main(int argc, char *argv[]) { assert(r >= 0); pa_gettimeofday(&start); - + pa_threaded_mainloop_start(m); for (k = 0; k < 5000; k++) { - int success = 0, changed = 0; + pa_bool_t success = FALSE, changed = FALSE; pa_usec_t t, rtc; struct timeval now, tv; - + pa_bool_t playing = FALSE; + pa_threaded_mainloop_lock(m); if (stream) { const pa_timing_info *info; - + if (pa_stream_get_time(stream, &t) >= 0) - success = 1; + success = TRUE; - 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; + if ((info = pa_stream_get_timing_info(stream))) { + if (memcmp(&last_info, &info->timestamp, sizeof(struct timeval))) { + changed = TRUE; last_info = info->timestamp; } + if (info->playing) + playing = TRUE; + } } - + pa_threaded_mainloop_unlock(m); - - if (success) { - pa_gettimeofday(&now); + pa_gettimeofday(&now); + + if (success) { rtc = pa_timeval_diff(&now, &start); - printf("%i\t%llu\t%llu\t%llu\t%llu\t%u\n", k, rtc, t, rtc-old_rtc, t-old_t, changed); + printf("%i\t%llu\t%llu\t%llu\t%llu\t%u\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, playing); + fflush(stdout); old_t = t; old_rtc = rtc; } @@ -156,7 +158,7 @@ int main(int argc, char *argv[]) { pa_stream_disconnect(stream); pa_stream_unref(stream); } - + if (context) { pa_context_disconnect(context); pa_context_unref(context); @@ -164,6 +166,6 @@ int main(int argc, char *argv[]) { if (m) pa_threaded_mainloop_free(m); - + return 0; } diff --git a/src/tests/ipacl-test.c b/src/tests/ipacl-test.c index 2566b038..bcdd469a 100644 --- a/src/tests/ipacl-test.c +++ b/src/tests/ipacl-test.c @@ -1,5 +1,3 @@ -/* $Id$ */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -37,13 +35,13 @@ int main(int argc, char *argv[]) { int r; pa_ip_acl *acl; - fd = socket(PF_INET, SOCK_STREAM, 0); + 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); @@ -66,7 +64,7 @@ int main(int argc, char *argv[]) { 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)); @@ -86,7 +84,7 @@ int main(int argc, char *argv[]) { 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); @@ -96,7 +94,7 @@ int main(int argc, char *argv[]) { 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); @@ -131,6 +129,6 @@ int main(int argc, char *argv[]) { pa_ip_acl_free(acl); close(fd); - + return 0; } diff --git a/src/tests/mainloop-test.c b/src/tests/mainloop-test.c index b06d0ed1..9fa2e466 100644 --- a/src/tests/mainloop-test.c +++ b/src/tests/mainloop-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -29,9 +27,9 @@ #include <assert.h> #include <pulse/timeval.h> +#include <pulse/gccmacro.h> #include <pulsecore/core-util.h> -#include <pulsecore/gccmacro.h> #ifdef GLIB_MAIN_LOOP @@ -121,6 +119,6 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { #else pa_mainloop_free(m); #endif - + return 0; } diff --git a/src/tests/mcalign-test.c b/src/tests/mcalign-test.c index 1584256c..9e358359 100644 --- a/src/tests/mcalign-test.c +++ b/src/tests/mcalign-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -31,9 +29,10 @@ #include <stdlib.h> #include <time.h> +#include <pulse/gccmacro.h> + #include <pulsecore/core-util.h> #include <pulsecore/mcalign.h> -#include <pulsecore/gccmacro.h> /* A simple program for testing pa_mcalign */ @@ -45,7 +44,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { p = pa_mempool_new(0); a = pa_mcalign_new(11); - + pa_memchunk_reset(&c); srand(time(NULL)); @@ -66,13 +65,15 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { 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); diff --git a/src/tests/memblock-test.c b/src/tests/memblock-test.c index c2dd2efa..6da1b1e9 100644 --- a/src/tests/memblock-test.c +++ b/src/tests/memblock-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -23,11 +21,11 @@ #include <config.h> #endif -#include <assert.h> #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) { @@ -46,24 +44,24 @@ static void print_stats(pa_mempool *p, const char *text) { "n_accumulated = %u\n" "n_imported = %u\n" "n_exported = %u\n" - "allocated_size = %lu\n" - "accumulated_size = %lu\n" - "imported_size = %lu\n" - "exported_size = %lu\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) AO_load_acquire_read((AO_t*) &s->n_allocated), - (unsigned) AO_load_acquire_read((AO_t*) &s->n_accumulated), - (unsigned) AO_load_acquire_read((AO_t*) &s->n_imported), - (unsigned) AO_load_acquire_read((AO_t*) &s->n_exported), - (unsigned long) AO_load_acquire_read((AO_t*) &s->allocated_size), - (unsigned long) AO_load_acquire_read((AO_t*) &s->accumulated_size), - (unsigned long) AO_load_acquire_read((AO_t*) &s->imported_size), - (unsigned long) AO_load_acquire_read((AO_t*) &s->exported_size), - (unsigned) AO_load_acquire_read((AO_t*) &s->n_too_large_for_pool), - (unsigned) AO_load_acquire_read((AO_t*) &s->n_pool_full)); + (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[]) { @@ -79,7 +77,7 @@ int main(int argc, char *argv[]) { 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); @@ -87,20 +85,20 @@ int main(int argc, char *argv[]) { 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); - - assert(pool_a && pool_b && pool_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[1]); + pa_memblock_release(blocks[2]); blocks[3] = pa_memblock_new_malloced(pool_a, pa_xstrdup(txt), sizeof(txt)); blocks[4] = NULL; @@ -109,35 +107,35 @@ int main(int argc, char *argv[]) { printf("Memory block %u\n", i); mb_a = blocks[i]; - assert(mb_a); - + 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"); - - assert(export_a && export_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"); - - assert(import_b && import_c); - + + pa_assert(import_b && import_c); + r = pa_memexport_put(export_a, mb_a, &id, &shm_id, &offset, &size); - assert(r >= 0); - assert(shm_id == id_a); + 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); - assert(mb_b); + pa_assert(mb_b); r = pa_memexport_put(export_b, mb_b, &id, &shm_id, &offset, &size); - assert(r >= 0); - assert(shm_id == id_a || shm_id == id_b); + 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); - assert(mb_c); + pa_assert(mb_c); x = pa_memblock_acquire(mb_c); printf("1 data=%s\n", x); pa_memblock_release(mb_c); @@ -145,23 +143,23 @@ int main(int argc, char *argv[]) { 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); diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c index 02848eb2..7bf992a1 100644 --- a/src/tests/memblockq-test.c +++ b/src/tests/memblockq-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -26,58 +24,85 @@ #include <stdlib.h> #include <assert.h> #include <stdio.h> +#include <signal.h> #include <pulsecore/memblockq.h> #include <pulsecore/log.h> +static void dump(pa_memblockq *bq) { + printf(">"); + + for (;;) { + pa_memchunk out; + char *e; + size_t n; + void *q; + + if (pa_memblockq_peek(bq, &out) < 0) + break; + + q = pa_memblock_acquire(out.memblock); + for (e = (char*) q + 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"); +} + int main(int argc, char *argv[]) { int ret; pa_mempool *p; pa_memblockq *bq; pa_memchunk chunk1, chunk2, chunk3, chunk4; - pa_memblock *silence; + pa_memchunk 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); + silence.memblock = pa_memblock_new_fixed(p, (char*) "__", 2, 1); + assert(silence.memblock); + silence.index = 0; + silence.length = pa_memblock_get_length(silence.memblock); + + bq = pa_memblockq_new(0, 40, 10, 2, 4, 4, 40, &silence); assert(bq); - chunk1.memblock = pa_memblock_new_fixed(p, (char*) "AA", 2, 1); + 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*) "TTBB", 4, 1); + + 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*) "ZZZZ", 4, 1); + 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*) "KKKKKKKK", 8, 1); + 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); + + ret = pa_memblockq_push(bq, &chunk3); + assert(ret == 0); + + ret = pa_memblockq_push(bq, &chunk4); assert(ret == 0); pa_memblockq_seek(bq, -6, 0); @@ -85,7 +110,7 @@ int main(int argc, char *argv[]) { assert(ret == 0); pa_memblockq_seek(bq, -2, 0); - ret = pa_memblockq_push(bq, &chunk3); + ret = pa_memblockq_push(bq, &chunk1); assert(ret == 0); pa_memblockq_seek(bq, -10, 0); @@ -115,39 +140,25 @@ int main(int argc, char *argv[]) { chunk3.index += 2; chunk3.length -= 2; - ret = pa_memblockq_push(bq, &chunk3); assert(ret == 0); - - printf(">"); - pa_memblockq_shorten(bq, 6); - - for (;;) { - pa_memchunk out; - char *e; - size_t n; - - if (pa_memblockq_peek(bq, &out) < 0) - break; + pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE); - 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); + dump(bq); + + pa_memblockq_rewind(bq, 52); + + dump(bq); - pa_memblock_unref(out.memblock); - pa_memblockq_drop(bq, &out, out.length); - } - - printf("<\n"); - pa_memblockq_free(bq); - pa_memblock_unref(silence); + pa_memblock_unref(silence.memblock); pa_memblock_unref(chunk1.memblock); pa_memblock_unref(chunk2.memblock); pa_memblock_unref(chunk3.memblock); pa_memblock_unref(chunk4.memblock); - + + pa_mempool_free(p); + return 0; } diff --git a/src/tests/mix-test.c b/src/tests/mix-test.c new file mode 100644 index 00000000..f3f6f829 --- /dev/null +++ b/src/tests/mix-test.c @@ -0,0 +1,259 @@ +/*** + 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 index 364e1ad6..b26e4b68 100644 --- a/src/tests/pacat-simple.c +++ b/src/tests/pacat-simple.c @@ -1,18 +1,16 @@ -/* $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 @@ -31,7 +29,7 @@ #include <pulse/simple.h> #include <pulse/error.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> #define BUFSIZE 1024 @@ -43,7 +41,7 @@ int main(PA_GCC_UNUSED int argc, char*argv[]) { .rate = 44100, .channels = 2 }; - + pa_simple *s = NULL; int ret = 1; int error; @@ -61,10 +59,10 @@ int main(PA_GCC_UNUSED int argc, char*argv[]) { 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)); @@ -90,7 +88,7 @@ int main(PA_GCC_UNUSED int argc, char*argv[]) { 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; } @@ -114,6 +112,6 @@ finish: if (s) pa_simple_free(s); - + return ret; } diff --git a/src/tests/parec-simple.c b/src/tests/parec-simple.c index 45a52288..6c0d529b 100644 --- a/src/tests/parec-simple.c +++ b/src/tests/parec-simple.c @@ -1,18 +1,16 @@ -/* $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 @@ -30,7 +28,7 @@ #include <pulse/simple.h> #include <pulse/error.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> #define BUFSIZE 1024 @@ -46,7 +44,7 @@ static ssize_t loop_write(int fd, const void*data, size_t size) { if (r == 0) break; - + ret += r; data = (const uint8_t*) data + r; size -= r; @@ -95,6 +93,6 @@ 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..20041af6 --- /dev/null +++ b/src/tests/proplist-test.c @@ -0,0 +1,60 @@ +/*** + 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> +#include <pulsecore/core-util.h> + +int main(int argc, char*argv[]) { + pa_proplist *a, *b; + char *s, *t; + + a = pa_proplist_new(); + pa_assert_se(pa_proplist_sets(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0); + pa_assert_se(pa_proplist_sets(a, PA_PROP_MEDIA_ARTIST, "Johann Sebastian Bach") == 0); + + b = pa_proplist_new(); + pa_assert_se(pa_proplist_sets(b, PA_PROP_MEDIA_TITLE, "Goldbergvariationen") == 0); + pa_assert_se(pa_proplist_set(b, PA_PROP_MEDIA_ICON, "\0\1\2\3\4\5\6\7", 8) == 0); + + pa_proplist_update(a, PA_UPDATE_MERGE, 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_unset(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..105f094a --- /dev/null +++ b/src/tests/queue-test.c @@ -0,0 +1,67 @@ +/*** + 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..4777c150 --- /dev/null +++ b/src/tests/remix-test.c @@ -0,0 +1,89 @@ +/*** + 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..1a20be2c --- /dev/null +++ b/src/tests/resampler-test.c @@ -0,0 +1,252 @@ +/*** + 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..953fd61d --- /dev/null +++ b/src/tests/rtpoll-test.c @@ -0,0 +1,91 @@ +/*** + 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_relative(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/rtstutter.c b/src/tests/rtstutter.c new file mode 100644 index 00000000..91e85c36 --- /dev/null +++ b/src/tests/rtstutter.c @@ -0,0 +1,117 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to 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 <time.h> +#include <sched.h> +#include <inttypes.h> +#include <string.h> +#include <pthread.h> + +#include <pulse/timeval.h> +#include <pulse/gccmacro.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +static int msec_lower, msec_upper; + +static void* work(void *p) PA_GCC_NORETURN; + +static void* work(void *p) { + cpu_set_t mask; + struct sched_param param; + + pa_log_notice("CPU%i: Created thread.", PA_PTR_TO_INT(p)); + + memset(¶m, 0, sizeof(param)); + param.sched_priority = 12; + pa_assert_se(pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m) == 0); + + CPU_ZERO(&mask); + CPU_SET(PA_PTR_TO_INT(p), &mask); + pa_assert_se(pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) == 0); + + for (;;) { + struct timespec now, end; + uint64_t nsec; + + pa_log_notice("CPU%i: Sleeping for 1s", PA_PTR_TO_INT(p)); + sleep(1); + + pa_assert_se(clock_gettime(CLOCK_REALTIME, &end) == 0); + + nsec = + (uint64_t) ((((double) rand())*(msec_upper-msec_lower)*PA_NSEC_PER_MSEC)/RAND_MAX) + + (uint64_t) (msec_lower*PA_NSEC_PER_MSEC); + + pa_log_notice("CPU%i: Freezing for %ims", PA_PTR_TO_INT(p), (int) (nsec/PA_NSEC_PER_MSEC)); + + end.tv_sec += nsec / PA_NSEC_PER_SEC; + end.tv_nsec += nsec % PA_NSEC_PER_SEC; + + while ((pa_usec_t) end.tv_nsec > PA_NSEC_PER_SEC) { + end.tv_sec++; + end.tv_nsec -= PA_NSEC_PER_SEC; + } + + do { + pa_assert_se(clock_gettime(CLOCK_REALTIME, &now) == 0); + } while (now.tv_sec < end.tv_sec || + (now.tv_sec == end.tv_sec && now.tv_nsec < end.tv_nsec)); + } +} + +int main(int argc, char*argv[]) { + int n; + + srand(time(NULL)); + + if (argc >= 3) { + msec_lower = atoi(argv[1]); + msec_upper = atoi(argv[2]); + } else if (argc >= 2) { + msec_lower = 0; + msec_upper = atoi(argv[1]); + } else { + msec_lower = 0; + msec_upper = 1000; + } + + pa_assert(msec_upper > 0); + pa_assert(msec_upper >= msec_lower); + + pa_log_notice("Creating random latencies in the range of %ims to %ims.", msec_lower, msec_upper); + + for (n = 1; n < sysconf(_SC_NPROCESSORS_CONF); n++) { + pthread_t t; + pa_assert_se(pthread_create(&t, NULL, work, PA_INT_TO_PTR(n)) == 0); + } + + work(PA_INT_TO_PTR(0)); + + return 0; +} diff --git a/src/tests/sig2str-test.c b/src/tests/sig2str-test.c new file mode 100644 index 00000000..d64a8902 --- /dev/null +++ b/src/tests/sig2str-test.c @@ -0,0 +1,37 @@ +/*** + 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..b78f3c91 --- /dev/null +++ b/src/tests/smoother-test.c @@ -0,0 +1,78 @@ +/*** + 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); u+= 2) { + + msec[u] = m+1 + (rand() % 100) - 50; + msec[u+1] = m + (rand() % 2000) - 1000; + + m += rand() % 100; + + if (msec[u] < 0) + msec[u] = 0; + + if (msec[u+1] < 0) + msec[u+1] = 0; + } + + s = pa_smoother_new(700*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, TRUE, 6); + + for (x = 0, u = 0; x < PA_USEC_PER_SEC * 10; x += PA_USEC_PER_MSEC) { + + while (u < PA_ELEMENTSOF(msec) && (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", (unsigned long long) (x/PA_USEC_PER_MSEC), (unsigned long long) (pa_smoother_get(s, x)/PA_USEC_PER_MSEC)); + } + + pa_smoother_free(s); + + return 0; +} diff --git a/src/tests/stripnul.c b/src/tests/stripnul.c new file mode 100644 index 00000000..0ab06776 --- /dev/null +++ b/src/tests/stripnul.c @@ -0,0 +1,70 @@ +/*** + 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 <string.h> +#include <inttypes.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +int main(int argc, char *argv[]) { + FILE *i, *o; + size_t granularity; + pa_bool_t found; + uint8_t *zero; + + pa_assert_se(argc >= 2); + pa_assert_se((granularity = atoi(argv[1])) >= 1); + pa_assert_se((i = (argc >= 3) ? fopen(argv[2], "r") : stdin)); + pa_assert_se((o = (argc >= 4) ? fopen(argv[3], "w") : stdout)); + + zero = pa_xmalloc0(granularity); + + for (;;) { + uint8_t buffer[16*1024], *p; + size_t k; + + k = fread(buffer, granularity, sizeof(buffer)/granularity, i); + + if (k <= 0) + break; + + if (found) + pa_assert_se(fwrite(buffer, granularity, k, o) == k); + else { + for (p = buffer; (p-buffer)/granularity < k; p += granularity) + if (memcmp(p, zero, granularity)) { + size_t left; + found = TRUE; + left = k - (p-buffer)/granularity; + pa_assert_se(fwrite(p, granularity, left, o) == left); + break; + } + } + } + + fflush(o); + + return 0; +} diff --git a/src/tests/strlist-test.c b/src/tests/strlist-test.c index 4262a001..2bd1645c 100644 --- a/src/tests/strlist-test.c +++ b/src/tests/strlist-test.c @@ -1,8 +1,9 @@ #include <stdio.h> #include <pulse/xmalloc.h> +#include <pulse/gccmacro.h> + #include <pulsecore/strlist.h> -#include <pulsecore/gccmacro.h> int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char* argv[]) { char *t, *u; @@ -16,7 +17,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char* argv[]) { t = pa_strlist_tostring(l); pa_strlist_free(l); - + fprintf(stderr, "1: %s\n", t); l = pa_strlist_parse(t); @@ -29,9 +30,9 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char* argv[]) { 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); diff --git a/src/tests/sync-playback.c b/src/tests/sync-playback.c index 39c6aac4..7ab3a25c 100644 --- a/src/tests/sync-playback.c +++ b/src/tests/sync-playback.c @@ -1,18 +1,16 @@ -/* $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 @@ -67,7 +65,7 @@ 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); @@ -89,19 +87,19 @@ static void stream_state_callback(pa_stream *s, void *userdata) { 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; } @@ -121,7 +119,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; - + case PA_CONTEXT_READY: { int i; @@ -131,18 +129,18 @@ static void context_state_callback(pa_context *c, void *userdata) { 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; @@ -163,7 +161,7 @@ int main(int argc, char *argv[]) { for (i = 0; i < NSTREAMS; i++) streams[i] = NULL; - + /* Set up a new main loop */ m = pa_mainloop_new(); assert(m); @@ -187,6 +185,6 @@ int main(int argc, char *argv[]) { 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 index bf3d4cd2..7a62f85a 100644 --- a/src/tests/thread-mainloop-test.c +++ b/src/tests/thread-mainloop-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -23,18 +21,19 @@ #include <config.h> #endif -#include <assert.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <pulse/timeval.h> #include <pulse/util.h> - -#include <pulsecore/gccmacro.h> #include <pulse/thread-mainloop.h> +#include <pulse/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"); @@ -45,27 +44,27 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { pa_threaded_mainloop *m; struct timeval tv; - m = pa_threaded_mainloop_new(); - assert(m); - a = pa_threaded_mainloop_get_api(m); - assert(a); + 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); diff --git a/src/tests/thread-test.c b/src/tests/thread-test.c index 9559cdbb..f29b5e71 100644 --- a/src/tests/thread-test.c +++ b/src/tests/thread-test.c @@ -1,18 +1,16 @@ -/* $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 @@ -42,13 +40,13 @@ static void once_func(void) { pa_log("once!"); } -static pa_once_t once = PA_ONCE_INIT; +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 (;;) { @@ -57,13 +55,13 @@ static void thread_func(void *data) { 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); } @@ -72,21 +70,21 @@ static void thread_func(void *data) { pa_mutex_unlock(mutex); - pa_once(&once, once_func); + 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)); @@ -97,25 +95,25 @@ int main(int argc, char *argv[]) { pa_thread* t[THREADS_MAX]; assert(pa_thread_is_running(pa_thread_self())); - - mutex = pa_mutex_new(0); + + 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); @@ -126,10 +124,10 @@ int main(int argc, char *argv[]) { } pa_log("loop-exit"); - + magic_number = -1; pa_cond_signal(cond1, 1); - + pa_mutex_unlock(mutex); for (i = 0; i < THREADS_MAX; i++) diff --git a/src/tests/utf8-test.c b/src/tests/utf8-test.c index 2e9f128a..f1708ad4 100644 --- a/src/tests/utf8-test.c +++ b/src/tests/utf8-test.c @@ -1,5 +1,3 @@ -/* $Id$ */ - #include <stdio.h> #include <assert.h> @@ -8,13 +6,13 @@ 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")); diff --git a/src/tests/voltest.c b/src/tests/voltest.c index 3de884af..d2c0ff69 100644 --- a/src/tests/voltest.c +++ b/src/tests/voltest.c @@ -1,9 +1,7 @@ -/* $Id$ */ - #include <stdio.h> #include <pulse/volume.h> -#include <pulsecore/gccmacro.h> +#include <pulse/gccmacro.h> int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { pa_volume_t v; @@ -12,7 +10,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { 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)); diff --git a/src/utils/pabrowse.c b/src/utils/pabrowse.c index 450182f5..f2ed9553 100644 --- a/src/utils/pabrowse.c +++ b/src/utils/pabrowse.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -40,7 +40,7 @@ static void dump_server(const pa_browse_info *i) { if (i->cookie) snprintf(t, sizeof(t), "0x%08x", *i->cookie); - + printf("server: %s\n" "server-version: %s\n" "user-name: %s\n" @@ -65,7 +65,7 @@ static void dump_device(const pa_browse_info *i) { 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) { @@ -89,7 +89,7 @@ static void browser_callback(pa_browser *b, pa_browse_opcode_t c, const pa_brows dump_server(i); dump_device(i); break; - + case PA_BROWSE_REMOVE_SERVER: printf("\n=> removed server <%s>\n", i->name); break; @@ -109,7 +109,7 @@ static void browser_callback(pa_browser *b, pa_browse_opcode_t c, const pa_brows 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); } @@ -128,7 +128,7 @@ int main(int argc, char *argv[]) { 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; @@ -136,7 +136,7 @@ int main(int argc, char *argv[]) { 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); diff --git a/src/utils/pacat.c b/src/utils/pacat.c index 1c581f4d..ee784a99 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -1,18 +1,19 @@ -/* $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 @@ -37,10 +38,12 @@ #define TIME_EVENT_USEC 50000 -#if PA_API_VERSION < 9 +#if PA_API_VERSION < 10 #error Invalid PulseAudio API version #endif +#define CLEAR_LINE "\x1B[K" + static enum { RECORD, PLAYBACK } mode = PLAYBACK; static pa_context *context = NULL; @@ -66,6 +69,10 @@ static pa_sample_spec sample_spec = { static pa_channel_map channel_map; static int channel_map_set = 0; +static pa_stream_flags_t flags = 0; + +static size_t latency = 0, process_time=0; + /* A shortcut for terminating the application */ static void quit(int ret) { assert(mainloop_api); @@ -79,20 +86,20 @@ static void do_stream_write(size_t 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; @@ -102,7 +109,8 @@ static void do_stream_write(size_t length) { /* 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 && length); + assert(s); + assert(length > 0); if (stdio_event) mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); @@ -116,7 +124,8 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { /* 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 && length); + assert(s); + assert(length > 0); if (stdio_event) mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); @@ -126,8 +135,9 @@ static void stream_read_callback(pa_stream *s, size_t length, void *userdata) { quit(1); return; } - - assert(data && length); + + assert(data); + assert(length > 0); if (buffer) { fprintf(stderr, "Buffer overrun, dropping incoming data\n"); @@ -156,7 +166,8 @@ static void stream_state_callback(pa_stream *s, void *userdata) { 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))) @@ -169,13 +180,20 @@ static void stream_state_callback(pa_stream *s, void *userdata) { 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)))); @@ -183,6 +201,45 @@ static void stream_state_callback(pa_stream *s, void *userdata) { } } +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." CLEAR_LINE " \n"); + else + fprintf(stderr, "Stream device resumed." CLEAR_LINE " \n"); + } +} + +static void stream_underflow_callback(pa_stream *s, void *userdata) { + assert(s); + + if (verbose) + fprintf(stderr, "Stream underrun." CLEAR_LINE " \n"); +} + +static void stream_overflow_callback(pa_stream *s, void *userdata) { + assert(s); + + if (verbose) + fprintf(stderr, "Stream overrun." CLEAR_LINE " \n"); +} + +static void stream_started_callback(pa_stream *s, void *userdata) { + assert(s); + + if (verbose) + fprintf(stderr, "Stream started." CLEAR_LINE " \n"); +} + +static void stream_moved_callback(pa_stream *s, void *userdata) { + assert(s); + + if (verbose) + fprintf(stderr, "Stream moved to device %s (%u, %ssuspended)." CLEAR_LINE " \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); @@ -192,14 +249,16 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; - + case PA_CONTEXT_READY: { int r; - - assert(c && !stream); + pa_buffer_attr buffer_attr; + + assert(c); + assert(!stream); if (verbose) - fprintf(stderr, "Connection established.\n"); + fprintf(stderr, "Connection established." CLEAR_LINE " \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))); @@ -209,24 +268,36 @@ static void context_state_callback(pa_context *c, void *userdata) { 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); + pa_stream_set_underflow_callback(stream, stream_underflow_callback, NULL); + pa_stream_set_overflow_callback(stream, stream_overflow_callback, NULL); + pa_stream_set_started_callback(stream, stream_started_callback, NULL); + + if (latency > 0) { + memset(&buffer_attr, 0, sizeof(buffer_attr)); + buffer_attr.tlength = latency; + buffer_attr.minreq = process_time; + flags |= PA_STREAM_ADJUST_LATENCY; + } if (mode == PLAYBACK) { pa_cvolume cv; - if ((r = pa_stream_connect_playback(stream, device, NULL, 0, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL)) < 0) { + if ((r = pa_stream_connect_playback(stream, device, latency > 0 ? &buffer_attr : 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, 0)) < 0) { + if ((r = pa_stream_connect_record(stream, device, latency > 0 ? &buffer_attr : 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; @@ -238,10 +309,10 @@ static void context_state_callback(pa_context *c, void *userdata) { } return; - + fail: quit(1); - + } /* Connection draining complete */ @@ -257,14 +328,14 @@ static void stream_drain_complete(pa_stream*s, int success, void *userdata) { fprintf(stderr, "Failed to drain stream: %s\n", pa_strerror(pa_context_errno(context))); quit(1); } - - if (verbose) + + 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 { @@ -277,7 +348,10 @@ static void stream_drain_complete(pa_stream*s, int success, void *userdata) { 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 && e && stdio_event == e); + + assert(a == mainloop_api); + assert(e); + assert(stdio_event == e); if (buffer) { mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); @@ -286,7 +360,7 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even 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) { @@ -296,17 +370,17 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even 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); @@ -327,7 +401,10 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even /* 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 && e && stdio_event == e); + + assert(a == mainloop_api); + assert(e); + assert(stdio_event == e); if (!buffer) { mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); @@ -335,7 +412,7 @@ static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_eve } 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); @@ -364,14 +441,14 @@ static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, /* Show the current latency */ static void stream_update_timing_callback(pa_stream *s, int success, void *userdata) { - pa_usec_t latency, usec; + pa_usec_t l, usec; int negative = 0; - + assert(s); if (!success || pa_stream_get_time(s, &usec) < 0 || - pa_stream_get_latency(s, &latency, &negative) < 0) { + pa_stream_get_latency(s, &l, &negative) < 0) { fprintf(stderr, "Failed to get latency: %s\n", pa_strerror(pa_context_errno(context))); quit(1); return; @@ -379,7 +456,7 @@ static void stream_update_timing_callback(pa_stream *s, int success, void *userd fprintf(stderr, "Time: %0.3f sec; Latency: %0.0f usec. \r", (float) usec / 1000000, - (float) latency * (negative?-1:1)); + (float) l * (negative?-1:1)); } /* Someone requested that the latency is shown */ @@ -387,13 +464,13 @@ static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int s 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))) @@ -426,7 +503,18 @@ static void help(const char *argv0) { " 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", + " --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" + " --latency=BYTES Request the specified latency in bytes.\n" + " --process-time=BYTES Request the specified process time per request in bytes.\n" + , argv0); } @@ -438,6 +526,13 @@ enum { ARG_SAMPLEFORMAT, ARG_CHANNELS, ARG_CHANNELMAP, + ARG_FIX_FORMAT, + ARG_FIX_RATE, + ARG_FIX_CHANNELS, + ARG_NO_REMAP, + ARG_NO_REMIX, + ARG_LATENCY, + ARG_PROCESS_TIME }; int main(int argc, char *argv[]) { @@ -447,21 +542,28 @@ int main(int argc, char *argv[]) { 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}, - {NULL, 0, NULL, 0} + {"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}, + {"latency", 1, NULL, ARG_LATENCY}, + {"process-time", 1, NULL, ARG_PROCESS_TIME}, + {NULL, 0, NULL, 0} }; if (!(bn = strrchr(argv[0], '/'))) @@ -481,7 +583,7 @@ int main(int argc, char *argv[]) { 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; @@ -525,7 +627,7 @@ int main(int argc, char *argv[]) { break; } - case ARG_CHANNELS: + case ARG_CHANNELS: sample_spec.channels = atoi(optarg); break; @@ -539,13 +641,47 @@ int main(int argc, char *argv[]) { case ARG_CHANNELMAP: if (!pa_channel_map_parse(&channel_map, optarg)) { - fprintf(stderr, "Invalid channel map\n"); + fprintf(stderr, "Invalid channel map '%s'\n", optarg); 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; + + case ARG_LATENCY: + if (((latency = atoi(optarg))) <= 0) { + fprintf(stderr, "Invalid latency specification '%s'\n", optarg); + goto quit; + } + break; + + case ARG_PROCESS_TIME: + if (((process_time = atoi(optarg))) <= 0) { + fprintf(stderr, "Invalid process time specification '%s'\n", optarg); + goto quit; + } + break; + default: goto quit; } @@ -560,7 +696,7 @@ int main(int argc, char *argv[]) { 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); @@ -570,22 +706,22 @@ int main(int argc, char *argv[]) { 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; @@ -616,7 +752,7 @@ int main(int argc, char *argv[]) { #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, @@ -641,7 +777,7 @@ int main(int argc, char *argv[]) { 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; @@ -653,7 +789,7 @@ int main(int argc, char *argv[]) { fprintf(stderr, "pa_mainloop_run() failed.\n"); goto quit; } - + quit: if (stream) pa_stream_unref(stream); @@ -670,7 +806,7 @@ quit: assert(mainloop_api); mainloop_api->time_free(time_event); } - + if (m) { pa_signal_done(); pa_mainloop_free(m); @@ -682,6 +818,6 @@ quit: 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 index b9912701..67d95252 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -34,6 +34,7 @@ #include <pulse/error.h> #include <pulse/util.h> +#include <pulse/xmalloc.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> @@ -47,8 +48,9 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { char ibuf[256], obuf[256]; size_t ibuf_index, ibuf_length, obuf_index, obuf_length; fd_set ifds, ofds; + char *cli; - if (pa_pid_file_check_running(&pid) < 0) { + if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) { pa_log("no PulseAudio daemon running"); goto fail; } @@ -60,25 +62,30 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; - pa_runtime_path("cli", sa.sun_path, sizeof(sa.sun_path)); + + if (!(cli = pa_runtime_path("cli"))) + goto fail; + + pa_strlcpy(sa.sun_path, cli, sizeof(sa.sun_path)); + pa_xfree(cli); 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) < 0) { + if (pa_pid_file_kill(SIGUSR2, NULL, "pulseaudio") < 0) { pa_log("failed to kill PulseAudio daemon."); goto fail; } - pa_msleep(50); + pa_msleep(300); } if (i >= 5) { @@ -94,7 +101,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { FD_SET(fd, &ifds); FD_ZERO(&ofds); - + for (;;) { if (select(FD_SETSIZE, &ifds, &ofds, NULL, NULL) < 0) { pa_log("select(): %s", strerror(errno)); @@ -104,19 +111,19 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { 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); @@ -124,7 +131,7 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { if ((r = read(fd, obuf, sizeof(obuf))) <= 0) { if (r == 0) break; - + pa_log("read(): %s", strerror(errno)); goto fail; } @@ -136,12 +143,12 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { 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; @@ -150,12 +157,12 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { 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; @@ -163,24 +170,24 @@ int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { 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 index 110585f7..4cca2f86 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -36,6 +36,7 @@ #include <sndfile.h> #include <pulse/pulseaudio.h> +#include <pulsecore/core-util.h> #if PA_API_VERSION < 10 #error Invalid PulseAudio API version @@ -46,8 +47,10 @@ 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; +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; @@ -67,7 +70,11 @@ static enum { REMOVE_SAMPLE, LIST, MOVE_SINK_INPUT, - MOVE_SOURCE_OUTPUT + MOVE_SOURCE_OUTPUT, + LOAD_MODULE, + UNLOAD_MODULE, + SUSPEND_SINK, + SUSPEND_SOURCE, } action = NONE; static void quit(int ret) { @@ -112,13 +119,13 @@ static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) 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); @@ -149,7 +156,8 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi 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]; - + char *pl; + if (is_last < 0) { fprintf(stderr, "Failed to get sink information: %s\n", pa_strerror(pa_context_errno(c))); quit(1); @@ -160,7 +168,7 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ complete_action(); return; } - + assert(i); if (nl) @@ -170,32 +178,37 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ 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", + "Monitor Source: %s\n" + "Latency: %0.0f usec, configured %0.0f usec\n" + "Flags: %s%s%s%s%s%s\n" + "Properties:\n%s", i->index, i->name, - i->driver, - i->description, + pa_strnull(i->driver), 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, + pa_strnull(i->monitor_source_name), + (double) i->latency, (double) i->configured_latency, + i->flags & PA_SINK_HARDWARE ? "HARDWARE " : "", + i->flags & PA_SINK_NETWORK ? "NETWORK " : "", + i->flags & PA_SINK_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + i->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", i->flags & PA_SINK_LATENCY ? "LATENCY " : "", - i->flags & PA_SINK_HARDWARE ? "HARDWARE" : ""); + pl = pa_proplist_to_string(i->proplist)); + pa_xfree(pl); } 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]; + char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + char *pl; if (is_last < 0) { fprintf(stderr, "Failed to get source information: %s\n", pa_strerror(pa_context_errno(c))); @@ -207,40 +220,42 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int 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", + "Latency: %0.0f usec, configured %0.0f usec\n" + "Flags: %s%s%s%s%s%s\n" + "Properties:\n%s", i->index, i->name, - i->driver, - i->description, + pa_strnull(i->driver), 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->monitor_of_sink_name ? i->monitor_of_sink_name : "n/a", + (double) i->latency, (double) i->configured_latency, + i->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "", + i->flags & PA_SOURCE_NETWORK ? "NETWORK " : "", + i->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + i->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", - i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : ""); + pl = pa_proplist_to_string(i->proplist)); + pa_xfree(pl); } static void get_module_info_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) { @@ -256,7 +271,7 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int complete_action(); return; } - + assert(i); if (nl) @@ -264,7 +279,7 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int nl = 1; snprintf(t, sizeof(t), "%u", i->n_used); - + printf("*** Module #%u ***\n" "Name: %s\n" "Argument: %s\n" @@ -272,13 +287,14 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int "Auto unload: %s\n", i->index, i->name, - i->argument, + i->argument ? i->argument : "", i->n_used != PA_INVALID_INDEX ? t : "n/a", - i->auto_unload ? "yes" : "no"); + pa_yes_no(i->auto_unload)); } static void get_client_info_callback(pa_context *c, const pa_client_info *i, int is_last, void *userdata) { char t[32]; + char *pl; if (is_last < 0) { fprintf(stderr, "Failed to get client information: %s\n", pa_strerror(pa_context_errno(c))); @@ -290,7 +306,7 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int complete_action(); return; } - + assert(i); if (nl) @@ -298,19 +314,22 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int nl = 1; snprintf(t, sizeof(t), "%u", i->owner_module); - + printf("*** Client #%u ***\n" - "Name: %s\n" "Driver: %s\n" - "Owner Module: %s\n", + "Owner Module: %s\n" + "Properties:\n%s", i->index, - i->name, - i->driver, - i->owner_module != PA_INVALID_INDEX ? t : "n/a"); + pa_strnull(i->driver), + i->owner_module != PA_INVALID_INDEX ? t : "n/a", + pl = pa_proplist_to_string(i->proplist)); + + pa_xfree(pl); } 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]; + char *pl; if (is_last < 0) { fprintf(stderr, "Failed to get sink input information: %s\n", pa_strerror(pa_context_errno(c))); @@ -322,7 +341,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info complete_action(); return; } - + assert(i); if (nl) @@ -331,9 +350,8 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info 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" @@ -343,24 +361,27 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info "Volume: %s\n" "Buffer Latency: %0.0f usec\n" "Sink Latency: %0.0f usec\n" - "Resample method: %s\n", + "Resample method: %s\n" + "Properties:\n%s", i->index, - i->name, - i->driver, + pa_strnull(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), - pa_cvolume_snprint(cv, sizeof(cv), &i->volume), + 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"); -} + i->resample_method ? i->resample_method : "n/a", + pl = pa_proplist_to_string(i->proplist)); + pa_xfree(pl); +} 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]; + char *pl; if (is_last < 0) { fprintf(stderr, "Failed to get source output information: %s\n", pa_strerror(pa_context_errno(c))); @@ -372,19 +393,18 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu 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" @@ -393,10 +413,10 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu "Channel Map: %s\n" "Buffer Latency: %0.0f usec\n" "Source Latency: %0.0f usec\n" - "Resample method: %s\n", + "Resample method: %s\n" + "Properties:\n%s", i->index, - i->name, - i->driver, + pa_strnull(i->driver), i->owner_module != PA_INVALID_INDEX ? t : "n/a", i->client != PA_INVALID_INDEX ? k : "n/a", i->source, @@ -404,11 +424,15 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu 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"); + i->resample_method ? i->resample_method : "n/a", + pl = pa_proplist_to_string(i->proplist)); + + pa_xfree(pl); } 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]; + char *pl; if (is_last < 0) { fprintf(stderr, "Failed to get sample information: %s\n", pa_strerror(pa_context_errno(c))); @@ -420,16 +444,16 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int 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" @@ -438,7 +462,8 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int "Duration: %0.1fs\n" "Size: %s\n" "Lazy: %s\n" - "Filename: %s\n", + "Filename: %s\n" + "Properties:\n%s", i->index, i->name, pa_cvolume_snprint(cv, sizeof(cv), &i->volume), @@ -446,8 +471,11 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int 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"); + pa_yes_no(i->lazy), + i->filename ? i->filename : "n/a", + pl = pa_proplist_to_string(i->proplist)); + + pa_xfree(pl); } static void get_autoload_info_callback(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata) { @@ -461,7 +489,7 @@ static void get_autoload_info_callback(pa_context *c, const pa_autoload_info *i, complete_action(); return; } - + assert(i); if (nl) @@ -477,7 +505,7 @@ static void get_autoload_info_callback(pa_context *c, const pa_autoload_info *i, i->name, i->type == PA_AUTOLOAD_SINK ? "sink" : "source", i->module, - i->argument); + i->argument ? i->argument : ""); } static void simple_callback(pa_context *c, int success, void *userdata) { @@ -490,6 +518,18 @@ static void simple_callback(pa_context *c, int success, void *userdata) { 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); @@ -497,11 +537,11 @@ static void stream_state_callback(pa_stream *s, void *userdata) { 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)))); @@ -524,7 +564,7 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { fprintf(stderr, "Premature end of file\n"); quit(1); } - + pa_stream_write(s, d, length, pa_xfree, 0, PA_SEEK_RELATIVE); sample_length -= length; @@ -551,7 +591,7 @@ static void context_state_callback(pa_context *c, void *userdata) { pa_operation_unref(pa_context_get_server_info(c, get_server_info_callback, NULL)); break; - case PLAY_SAMPLE: + case PLAY_SAMPLE: pa_operation_unref(pa_context_play_sample(c, sample_name, device, PA_VOLUME_NORM, simple_callback, NULL)); break; @@ -562,12 +602,12 @@ static void context_state_callback(pa_context *c, void *userdata) { 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(); @@ -578,7 +618,7 @@ static void context_state_callback(pa_context *c, void *userdata) { 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_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)); @@ -591,7 +631,29 @@ static void context_state_callback(pa_context *c, void *userdata) { 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); } @@ -620,14 +682,18 @@ static void help(const char *argv0) { "%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] remove-sample NAME\n\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, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0); } enum { ARG_VERSION = 256 }; @@ -650,14 +716,14 @@ int main(int argc, char *argv[]) { 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; @@ -680,7 +746,7 @@ int main(int argc, char *argv[]) { if (!client_name) client_name = pa_xstrdup(bn); - + if (optind < argc) { if (!strcmp(argv[optind], "stat")) action = STAT; @@ -712,13 +778,13 @@ int main(int argc, char *argv[]) { 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; @@ -726,7 +792,7 @@ int main(int argc, char *argv[]) { sample_length = sfinfo.frames*pa_frame_size(&sample_spec); } else if (!strcmp(argv[optind], "play-sample")) { action = PLAY_SAMPLE; - if (optind+1 >= argc) { + if (argc != optind+2 && argc != optind+3) { fprintf(stderr, "You have to specify a sample name to play\n"); goto quit; } @@ -735,10 +801,10 @@ int main(int argc, char *argv[]) { if (optind+2 < argc) device = pa_xstrdup(argv[optind+2]); - + } else if (!strcmp(argv[optind], "remove-sample")) { action = REMOVE_SAMPLE; - if (optind+1 >= argc) { + if (argc != optind+2) { fprintf(stderr, "You have to specify a sample name to remove\n"); goto quit; } @@ -746,7 +812,7 @@ int main(int argc, char *argv[]) { sample_name = pa_xstrdup(argv[optind+1]); } else if (!strcmp(argv[optind], "move-sink-input")) { action = MOVE_SINK_INPUT; - if (optind+2 >= argc) { + if (argc != optind+3) { fprintf(stderr, "You have to specify a sink input index and a sink\n"); goto quit; } @@ -755,13 +821,76 @@ int main(int argc, char *argv[]) { sink_name = pa_xstrdup(argv[optind+2]); } else if (!strcmp(argv[optind], "move-source-output")) { action = MOVE_SOURCE_OUTPUT; - if (optind+2 >= argc) { + 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]); + } else if (!strcmp(argv[optind], "help")) { + help(bn); + ret = 0; + goto quit; } } @@ -783,7 +912,7 @@ int main(int argc, char *argv[]) { #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; @@ -808,7 +937,7 @@ quit: pa_signal_done(); pa_mainloop_free(m); } - + if (sndfile) sf_close(sndfile); @@ -817,6 +946,8 @@ quit: 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 index bae5a728..4fe175c2 100755 --- a/src/utils/padsp +++ b/src/utils/padsp @@ -1,9 +1,10 @@ #!/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 diff --git a/src/utils/padsp.c b/src/utils/padsp.c index 5d96a984..d650707e 100644 --- a/src/utils/padsp.c +++ b/src/utils/padsp.c @@ -1,18 +1,19 @@ -/* $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 @@ -50,14 +51,18 @@ #endif #include <pulse/pulseaudio.h> +#include <pulse/gccmacro.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, @@ -69,7 +74,7 @@ struct fd_info { pthread_mutex_t mutex; int ref; int unusable; - + fd_info_type_t type; int app_fd, thread_fd; @@ -81,6 +86,8 @@ struct fd_info { 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; @@ -95,7 +102,7 @@ struct fd_info { int volume_modify_count; int optr_n_blocks; - + PA_LLIST_FIELDS(fd_info); }; @@ -111,9 +118,17 @@ 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; @@ -165,6 +180,38 @@ do { \ 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); \ @@ -191,32 +238,32 @@ do { \ #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", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ goto label; \ } \ -} while(0); +} 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", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ goto label; \ } \ -} while(0); +} 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", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ goto label; \ } \ -} while(0); +} 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 +#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; @@ -248,26 +295,25 @@ static int padsp_disabled(void) { * -> 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")) + + if (getenv("PADSP_NO_DSP") || getenv("PULSE_INTERNAL")) return 0; return 1; @@ -277,7 +323,7 @@ static int sndstat_cloak_enable(void) { if (padsp_disabled() & 2) return 0; - if (getenv("PADSP_NO_SNDSTAT")) + if (getenv("PADSP_NO_SNDSTAT") || getenv("PULSE_INTERNAL")) return 0; return 1; @@ -287,7 +333,7 @@ static int mixer_cloak_enable(void) { if (padsp_disabled() & 4) return 0; - if (getenv("PADSP_NO_MIXER")) + if (getenv("PADSP_NO_MIXER") || getenv("PULSE_INTERNAL")) return 0; return 1; @@ -302,7 +348,7 @@ 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; @@ -320,10 +366,10 @@ static void fd_info_free(fd_info *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); @@ -338,7 +384,7 @@ static void fd_info_free(fd_info *i) { pa_context_disconnect(i->context); pa_context_unref(i->context); } - + if (i->mainloop) pa_threaded_mainloop_free(i->mainloop); @@ -360,7 +406,7 @@ static void fd_info_free(fd_info *i) { static fd_info *fd_info_ref(fd_info *i) { assert(i); - + pthread_mutex_lock(&i->mutex); assert(i->ref >= 1); i->ref++; @@ -376,7 +422,7 @@ static void fd_info_unref(fd_info *i) { pthread_mutex_lock(&i->mutex); assert(i->ref >= 1); r = --i->ref; - debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref--, now %i\n", i->ref); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref--, now %i\n", i->ref); pthread_mutex_unlock(&i->mutex); if (r <= 0) @@ -404,7 +450,7 @@ static void context_state_cb(pa_context *c, void *userdata) { 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; @@ -418,7 +464,7 @@ static const char *client_name(char *buf, size_t n) { if ((e = getenv("PADSP_CLIENT_NAME"))) return e; - + if (pa_get_binary_name(p, sizeof(p))) snprintf(buf, n, "OSS Emulation[%s]", p); else @@ -440,7 +486,7 @@ 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); @@ -452,13 +498,13 @@ static void atfork_prepare(void) { 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); @@ -471,19 +517,19 @@ static void atfork_parent(void) { 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); @@ -556,7 +602,7 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { 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; @@ -569,6 +615,8 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { 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); @@ -638,12 +686,12 @@ static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { unlock_and_fail: pa_threaded_mainloop_unlock(i->mainloop); - + fail: if (i) fd_info_unref(i); - + return NULL; } @@ -671,7 +719,7 @@ 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); @@ -679,7 +727,7 @@ static fd_info* fd_info_find(int fd) { } pthread_mutex_unlock(&fd_infos_mutex); - + return i; } @@ -907,9 +955,21 @@ static void stream_state_cb(pa_stream *s, void * userdata) { case PA_STREAM_READY: debug(DEBUG_LEVEL_NORMAL, __FILE__": stream established.\n"); break; - + case PA_STREAM_FAILED: - debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + 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; @@ -922,8 +982,8 @@ static void stream_state_cb(pa_stream *s, void * userdata) { static int create_playback_stream(fd_info *i) { pa_buffer_attr attr; - int n; - + int n, flags; + assert(i); fix_metrics(i); @@ -942,8 +1002,13 @@ static int create_playback_stream(fd_info *i) { attr.tlength = i->fragment_size * i->n_fragments; attr.prebuf = i->fragment_size; attr.minreq = i->fragment_size; - - if (pa_stream_connect_playback(i->play_stream, NULL, &attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL) < 0) { + + 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; } @@ -952,7 +1017,7 @@ static int create_playback_stream(fd_info *i) { 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: @@ -961,8 +1026,8 @@ fail: static int create_record_stream(fd_info *i) { pa_buffer_attr attr; - int n; - + int n, flags; + assert(i); fix_metrics(i); @@ -979,9 +1044,14 @@ static int create_record_stream(fd_info *i) { memset(&attr, 0, sizeof(attr)); attr.maxlength = i->fragment_size * (i->n_fragments+1); attr.fragsize = i->fragment_size; - - if (pa_stream_connect_record(i->rec_stream, NULL, &attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE) < 0) { - debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + + 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; } @@ -989,7 +1059,7 @@ static int create_record_stream(fd_info *i) { 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: @@ -1025,7 +1095,7 @@ static void io_event_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_even fd_info *i = userdata; pa_threaded_mainloop_signal(i->mainloop, 0); - + if (flags & PA_IO_EVENT_INPUT) { if (!i->play_stream) { @@ -1035,7 +1105,7 @@ static void io_event_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_even if (fd_info_copy_data(i, 0) < 0) goto fail; } - + } else if (flags & PA_IO_EVENT_OUTPUT) { if (!i->rec_stream) { @@ -1050,7 +1120,7 @@ static void io_event_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_even goto fail; return; - + fail: /* We can't do anything better than removing the event source */ fd_info_shutdown(i); @@ -1100,7 +1170,7 @@ static int dsp_open(int flags, int *_errno) { 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); @@ -1108,7 +1178,7 @@ static int dsp_open(int flags, int *_errno) { fd_info_add_to_list(i); ret = i->app_fd; fd_info_unref(i); - + return ret; fail: @@ -1116,7 +1186,7 @@ fail: if (i) fd_info_unref(i); - + *_errno = EIO; debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open() failed\n"); @@ -1138,7 +1208,7 @@ static void sink_info_cb(pa_context *context, const pa_sink_info *si, int eol, v if (!pa_cvolume_equal(&i->sink_volume, &si->volume)) i->volume_modify_count++; - + i->sink_volume = si->volume; i->sink_index = si->index; @@ -1160,7 +1230,7 @@ static void source_info_cb(pa_context *context, const pa_source_info *si, int eo if (!pa_cvolume_equal(&i->source_volume, &si->volume)) i->volume_modify_count++; - + i->source_volume = si->volume; i->source_index = si->index; @@ -1193,13 +1263,13 @@ static int mixer_open(int flags, int *_errno) { debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open()\n"); - if (!(i = fd_info_new(FD_INFO_MIXER, _errno))) + 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; @@ -1274,7 +1344,7 @@ static int mixer_open(int flags, int *_errno) { fd_info_add_to_list(i); ret = i->app_fd; fd_info_unref(i); - + return ret; fail: @@ -1285,7 +1355,7 @@ fail: if (i) fd_info_unref(i); - + *_errno = EIO; debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open() failed\n"); @@ -1323,10 +1393,10 @@ static int sndstat_open(int flags, int *_errno) { int e; debug(DEBUG_LEVEL_NORMAL, __FILE__": sndstat_open()\n"); - + if (flags != O_RDONLY #ifdef O_LARGEFILE - && flags != (O_RDONLY|O_LARGEFILE) + && flags != (O_RDONLY|O_LARGEFILE) #endif ) { *_errno = EACCES; @@ -1367,50 +1437,55 @@ fail: return -1; } -int open(const char *filename, int flags, ...) { - va_list args; - mode_t mode = 0; +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); - - va_start(args, flags); - if (flags & O_CREAT) { - if (sizeof(mode_t) < sizeof(int)) - mode = va_arg(args, int); - else - mode = va_arg(args, mode_t); - } - va_end(args); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": open(%s)\n", filename?filename:"NULL"); if (!function_enter()) { LOAD_OPEN_FUNC(); return _open(filename, flags, mode); } - if (dsp_cloak_enable() && (strcmp(filename, "/dev/dsp") == 0 || strcmp(filename, "/dev/adsp") == 0)) { + if (filename && dsp_cloak_enable() && (strcmp(filename, "/dev/dsp") == 0 || strcmp(filename, "/dev/adsp") == 0)) r = dsp_open(flags, &_errno); - } else if (mixer_cloak_enable() && strcmp(filename, "/dev/mixer") == 0) { + else if (filename && mixer_cloak_enable() && strcmp(filename, "/dev/mixer") == 0) r = mixer_open(flags, &_errno); - } else if (sndstat_cloak_enable() && strcmp(filename, "/dev/sndstat") == 0) { + else if (filename && sndstat_cloak_enable() && strcmp(filename, "/dev/sndstat") == 0) r = sndstat_open(flags, &_errno); - } else { + 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"); @@ -1423,7 +1498,7 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno *(int*) argp = SOUND_MASK_IGAIN; break; - + case SOUND_MIXER_READ_STEREODEVS: debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_STEREODEVS\n"); @@ -1434,7 +1509,7 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno if (i->source_volume.channels > 1) *(int*) argp |= SOUND_MASK_IGAIN; pa_threaded_mainloop_unlock(i->mainloop); - + break; case SOUND_MIXER_READ_RECSRC: @@ -1452,7 +1527,7 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno *(int*) argp = 0; break; - + case SOUND_MIXER_READ_PCM: case SOUND_MIXER_READ_IGAIN: { pa_cvolume *v; @@ -1482,14 +1557,14 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno case SOUND_MIXER_WRITE_IGAIN: { pa_cvolume v, *pv; - if (request == SOUND_MIXER_READ_PCM) + 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_READ_PCM) { + if (request == SOUND_MIXER_WRITE_PCM) { v = i->sink_volume; pv = &i->sink_volume; } else { @@ -1503,7 +1578,7 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno if (!pa_cvolume_equal(pv, &v)) { pa_operation *o; - if (request == SOUND_MIXER_READ_PCM) + 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); @@ -1515,23 +1590,23 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno 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; } @@ -1548,7 +1623,7 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno pa_threaded_mainloop_unlock(i->mainloop); break; } - + default: debug(DEBUG_LEVEL_NORMAL, __FILE__": unknown ioctl 0x%08lx\n", request); @@ -1557,44 +1632,44 @@ static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno } 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; @@ -1666,14 +1741,14 @@ static int dsp_flush_socket(fd_info *i) { 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; @@ -1683,7 +1758,7 @@ static int dsp_empty_socket(fd_info *i) { ret = 0; break; } - + pa_threaded_mainloop_wait(i->mainloop); } @@ -1700,19 +1775,19 @@ static int dsp_drain(fd_info *i) { 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; @@ -1721,7 +1796,7 @@ static int dsp_drain(fd_info *i) { 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); } @@ -1731,9 +1806,9 @@ static int dsp_drain(fd_info *i) { } r = 0; - + fail: - + if (o) pa_operation_unref(o); @@ -1755,7 +1830,7 @@ static int dsp_trigger(fd_info *i) { 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; @@ -1764,7 +1839,7 @@ static int dsp_trigger(fd_info *i) { 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); } @@ -1774,9 +1849,47 @@ static int dsp_trigger(fd_info *i) { } 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); @@ -1787,11 +1900,21 @@ fail: 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) @@ -1804,12 +1927,12 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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); @@ -1821,7 +1944,7 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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); @@ -1833,24 +1956,24 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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; @@ -1860,7 +1983,7 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) i->sample_spec = ss; free_streams(i); } - + pa_threaded_mainloop_unlock(i->mainloop); if (!valid) { @@ -1878,16 +2001,16 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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; @@ -1896,30 +2019,30 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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 + + *(int*) argp = DSP_CAP_DUPLEX | DSP_CAP_TRIGGER #ifdef DSP_CAP_MULTI - | 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; @@ -1937,10 +2060,10 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) pa_threaded_mainloop_wait(i->mainloop); } - + exit_loop: -#ifdef SIOCINQ +#ifdef SIOCINQ if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno)); else @@ -1955,39 +2078,76 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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) + + if (dsp_trigger(i) < 0) *_errno = EIO; break; - case SNDCTL_DSP_SYNC: + 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) + + if (dsp_drain(i) < 0) *_errno = EIO; break; @@ -2055,36 +2215,36 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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; + 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; + 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; - + 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 (;;) { @@ -2095,7 +2255,7 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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; @@ -2111,7 +2271,7 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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); @@ -2122,9 +2282,15 @@ static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) 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: - debug(DEBUG_LEVEL_NORMAL, __FILE__": unknown ioctl 0x%08lx\n", request); + /* Mixer ioctls are valid on /dev/dsp aswell */ + return mixer_ioctl(i, request, argp, _errno); inval: *_errno = EINVAL; @@ -2132,13 +2298,17 @@ inval: } ret = 0; - + fail: - + return ret; } +#ifdef sun +int ioctl(int fd, int request, ...) { +#else int ioctl(int fd, unsigned long request, ...) { +#endif fd_info *i; va_list args; void *argp; @@ -2165,14 +2335,14 @@ int ioctl(int fd, unsigned long request, ...) { 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; } @@ -2194,7 +2364,7 @@ int close(int fd) { fd_info_remove_from_list(i); fd_info_unref(i); - + function_exit(); return 0; @@ -2202,18 +2372,13 @@ int close(int fd) { int access(const char *pathname, int mode) { - if (!pathname) { - /* Firefox needs this. See #27 */ - errno = EFAULT; - return -1; - } - - debug(DEBUG_LEVEL_VERBOSE, __FILE__": access(%s)\n", pathname); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": access(%s)\n", pathname?pathname:"NULL"); - if (strcmp(pathname, "/dev/dsp") != 0 && - strcmp(pathname, "/dev/adsp") != 0 && - strcmp(pathname, "/dev/sndstat") != 0 && - strcmp(pathname, "/dev/mixer") != 0) { + 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); } @@ -2229,43 +2394,194 @@ int access(const char *pathname, int 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); - - va_start(args, flags); - if (flags & O_CREAT) - mode = va_arg(args, mode_t); - va_end(args); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": open64(%s)\n", filename?filename:"NULL"); - if (strcmp(filename, "/dev/dsp") != 0 && - strcmp(filename, "/dev/adsp") != 0 && - strcmp(filename, "/dev/sndstat") != 0 && - strcmp(filename, "/dev/mixer") != 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); + } + + 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 open(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); - if (strcmp(filename, "/dev/dsp") != 0 && - strcmp(filename, "/dev/adsp") != 0 && - strcmp(filename, "/dev/sndstat") != 0 && - strcmp(filename, "/dev/mixer") != 0) { + 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); } @@ -2286,14 +2602,14 @@ FILE* fopen(const char *filename, const char *mode) { if ((((mode[1] == 'b') || (mode[1] == 't')) && (mode[2] == '+')) || (mode[1] == '+')) m = O_RDWR; - if ((fd = open(filename, m)) < 0) + if ((fd = real_open(filename, m, 0)) < 0) return NULL; if (!(f = fdopen(fd, mode))) { close(fd); return NULL; } - + return f; } @@ -2301,12 +2617,14 @@ FILE* fopen(const char *filename, const char *mode) { FILE *fopen64(const char *filename, const char *mode) { - debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen64(%s)\n", filename); + debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen64(%s)\n", filename?filename:"NULL"); - if (strcmp(filename, "/dev/dsp") != 0 && - strcmp(filename, "/dev/adsp") != 0 && - strcmp(filename, "/dev/sndstat") != 0 && - strcmp(filename, "/dev/mixer") != 0) { + 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); } @@ -2337,9 +2655,9 @@ int fclose(FILE *f) { /* 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(); diff --git a/src/utils/paplay.c b/src/utils/paplay.c index 0386c9df..1b6228b1 100644 --- a/src/utils/paplay.c +++ b/src/utils/paplay.c @@ -1,18 +1,19 @@ -/* $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 @@ -77,14 +78,14 @@ static void stream_drain_complete(pa_stream*s, int success, void *userdata) { fprintf(stderr, "Failed to drain stream: %s\n", pa_strerror(pa_context_errno(context))); quit(1); } - - if (verbose) + + 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 { @@ -111,7 +112,7 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { if ((bytes = readf_function(sndfile, data, length/k)) > 0) bytes *= k; - + } else bytes = sf_read_raw(sndfile, data, length); @@ -120,7 +121,7 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { else pa_xfree(data); - if (bytes < length) { + if (bytes < (sf_count_t) length) { sf_close(sndfile); sndfile = NULL; pa_operation_unref(pa_stream_drain(s, stream_drain_complete, NULL)); @@ -140,7 +141,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) { 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)))); @@ -157,10 +158,10 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; - + case PA_CONTEXT_READY: { pa_cvolume cv; - + assert(c && !stream); if (verbose) @@ -172,10 +173,10 @@ static void context_state_callback(pa_context *c, void *userdata) { 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; @@ -192,7 +193,7 @@ static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, if (verbose) fprintf(stderr, "Got SIGINT, exiting.\n"); quit(0); - + } static void help(const char *argv0) { @@ -200,9 +201,9 @@ 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 operations\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/source 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" @@ -251,7 +252,7 @@ int main(int argc, char *argv[]) { 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; @@ -302,7 +303,7 @@ int main(int argc, char *argv[]) { } filename = optind < argc ? argv[optind] : "STDIN"; - + memset(&sfinfo, 0, sizeof(sfinfo)); if (optind < argc) @@ -317,9 +318,9 @@ int main(int argc, char *argv[]) { 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: @@ -327,11 +328,11 @@ int main(int argc, char *argv[]) { 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; @@ -369,13 +370,13 @@ int main(int argc, char *argv[]) { 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"); @@ -390,7 +391,7 @@ int main(int argc, char *argv[]) { #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"); @@ -407,7 +408,7 @@ int main(int argc, char *argv[]) { fprintf(stderr, "pa_mainloop_run() failed.\n"); goto quit; } - + quit: if (stream) pa_stream_unref(stream); @@ -427,6 +428,6 @@ quit: 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..5b4885db --- /dev/null +++ b/src/utils/pasuspender.c @@ -0,0 +1,316 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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 index 6a3c6dbc..eee7b6a8 100644 --- a/src/utils/pax11publish.c +++ b/src/utils/pax11publish.c @@ -1,18 +1,18 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - + + Copyright 2004-2006 Lennart Poettering + PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -99,7 +99,7 @@ int main(int argc, char *argv[]) { switch (mode) { case DUMP: { char t[1024]; - if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t))) + 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); @@ -110,10 +110,10 @@ int main(int argc, char *argv[]) { break; } - + case IMPORT: { char t[1024]; - if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t))) + 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); @@ -158,7 +158,7 @@ int main(int argc, char *argv[]) { 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) @@ -169,7 +169,7 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Failed to get FQDN.\n"); goto finish; } - + pa_x11_set_prop(d, "PULSE_SERVER", hn); } @@ -184,7 +184,7 @@ int main(int argc, char *argv[]) { 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; @@ -201,20 +201,20 @@ int main(int argc, char *argv[]) { 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; } @@ -1,10 +1,7 @@ -*** $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. -- build pulsecore only statically by default, it's not a public API yet Porting: - rtp module ported to Win32 (sendmsg/recvmsg emulation) @@ -12,17 +9,12 @@ Porting: I18N: - iconv stuff sent from utils to server (UTF-8) - iconv sample loading in server -- Document utf8.h, timeval.h and util.h -- gettextify polypaudio +- gettextify pulseaudio Cleanups: -- drop dependency of libpolyp on libX11, instead use an external mini binary -- merge module-oss-mmap into module-oss +- drop dependency of libpulse 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) -- silence generation should be moved into the core to avoid races and code - duplication in the backends -- don't read/write audio data from/to ALSA devices if noone is listening - using POSIX monotonous clocks wherever possible instead of gettimeofday() Test: @@ -35,24 +27,19 @@ Auth/Crypto: - sasl auth Features: -- alsa mmap driver -- 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 -- "include" command in configuration files. should have glob support. +- better ".include" command in configuration files. should have glob support. +- recursive .if Long term: - pass meta info for hearing impaired @@ -60,5 +47,3 @@ Long term: Backends for: - portaudio (semi-done) -- sdl -- xine (needs update) |