diff options
145 files changed, 7177 insertions, 1880 deletions
@@ -1,3 +1,4 @@ +.version shave shave-libtool .*.swp diff --git a/Makefile.am b/Makefile.am index 9a3ca7b7..2448e748 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,7 +52,7 @@ 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).git`date +%Y%m%d`.tar.gz + cp $(distdir).tar.gz $$HOME/cvs.fedora/pulseaudio/devel/$(distdir).tar.gz dist-hook: if test -d .git ; then \ @@ -60,6 +60,7 @@ dist-hook: chmod u+w ${distdir}/ChangeLog || true ; \ ( git-changelog.perl || echo "git-changelog.perl failed." ) > ${distdir}/ChangeLog 2>&1 ; \ fi + echo $(VERSION) > $(distdir)/.tarball-version update-shave: for i in shave.in shave.m4 shave-libtool.in; do \ @@ -69,5 +70,10 @@ update-shave: .PHONY: homepage distcleancheck doxygen +# see git-version-gen +BUILT_SOURCES = $(top_srcdir)/.version +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ + DISTCLEANFILES = \ po/.intltool-merge-cache diff --git a/bootstrap.sh b/bootstrap.sh index cb74121e..c7737a6e 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -40,6 +40,12 @@ run_versioned() { set -ex +if [ -f .git/hooks/pre-commit.sample -a ! -f .git/hooks/pre-commit ] ; then + echo "Activating pre-commit hook." + cp -av .git/hooks/pre-commit.sample .git/hooks/pre-commit + chmod -c +x .git/hooks/pre-commit +fi + # We check for this here, because if pkg-config is not found in the # system, it's likely that the pkg.m4 macro file is also not present, # which will make PKG_PROG_PKG_CONFIG be undefined and the generated diff --git a/configure.ac b/configure.ac index 25cee577..45223001 100644 --- a/configure.ac +++ b/configure.ac @@ -22,16 +22,17 @@ AC_PREREQ(2.63) -m4_define(pa_major, [0]) -m4_define(pa_minor, [9]) -m4_define(pa_micro, [15]) - -AC_INIT([pulseaudio],[pa_major.pa_minor.pa_micro],[mzchyfrnhqvb (at) 0pointer (dot) net]) +AC_INIT([pulseaudio], m4_esyscmd([./git-version-gen .tarball-version]), + [mzchyfrnhqvb (at) 0pointer (dot) net]) AC_CONFIG_SRCDIR([src/daemon/main.c]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([foreign 1.10 -Wall -Wno-portability]) +m4_define(pa_major, `echo $VERSION | cut -d. -f 1`) +m4_define(pa_minor, `echo $VERSION | cut -d. -f 2`) +m4_define(pa_micro, `echo $VERSION | cut -d. -f 3`) + AC_SUBST(PA_MAJOR, pa_major) AC_SUBST(PA_MINOR, pa_minor) AC_SUBST(PA_MICRO, pa_micro) @@ -300,7 +301,7 @@ AM_CONDITIONAL(OS_IS_WIN32, test "x$os_is_win32" = "x1") AC_HEADER_STDC # POSIX -AC_CHECK_HEADERS([arpa/inet.h glob.h grp.h netdb.h netinet/in.h \ +AC_CHECK_HEADERS_ONCE([arpa/inet.h glob.h grp.h netdb.h netinet/in.h \ netinet/in_systm.h netinet/tcp.h poll.h pwd.h sched.h \ sys/mman.h sys/resource.h sys/select.h sys/socket.h sys/wait.h \ sys/uio.h syslog.h sys/dl.h dlfcn.h linux/sockios.h]) @@ -324,23 +325,23 @@ AC_CHECK_HEADERS([linux/input.h], [HAVE_EVDEV=1], [HAVE_EVDEV=0]) AM_CONDITIONAL([HAVE_EVDEV], [test "x$HAVE_EVDEV" = "x1"]) -AC_CHECK_HEADERS([sys/prctl.h]) +AC_CHECK_HEADERS_ONCE([sys/prctl.h]) # Solaris -AC_CHECK_HEADERS([sys/filio.h]) +AC_CHECK_HEADERS_ONCE([sys/filio.h]) # Windows -AC_CHECK_HEADERS([windows.h winsock2.h ws2tcpip.h]) +AC_CHECK_HEADERS_ONCE([windows.h winsock2.h ws2tcpip.h]) # NetBSD -AC_CHECK_HEADERS([sys/atomic.h]) +AC_CHECK_HEADERS_ONCE([sys/atomic.h]) # Other -AC_CHECK_HEADERS([sys/ioctl.h]) -AC_CHECK_HEADERS([byteswap.h]) -AC_CHECK_HEADERS([sys/syscall.h]) -AC_CHECK_HEADERS([sys/eventfd.h]) -AC_CHECK_HEADERS([execinfo.h]) +AC_CHECK_HEADERS_ONCE([sys/ioctl.h]) +AC_CHECK_HEADERS_ONCE([byteswap.h]) +AC_CHECK_HEADERS_ONCE([sys/syscall.h]) +AC_CHECK_HEADERS_ONCE([sys/eventfd.h]) +AC_CHECK_HEADERS_ONCE([execinfo.h]) #### Typdefs, structures, etc. #### @@ -391,19 +392,16 @@ 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, [], [AC_MSG_ERROR([gdbm.h not found])]) - #### Check for functions #### # ISO -AC_CHECK_FUNCS([lrintf strtof]) +AC_CHECK_FUNCS_ONCE([lrintf strtof]) # POSIX AC_FUNC_FORK AC_FUNC_GETGROUPS AC_FUNC_SELECT_ARGTYPES -AC_CHECK_FUNCS([chmod chown clock_gettime getaddrinfo getgrgid_r getgrnam_r \ +AC_CHECK_FUNCS_ONCE([chmod chown clock_gettime getaddrinfo getgrgid_r getgrnam_r \ getpwnam_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 pthread_setaffinity_np]) @@ -412,20 +410,20 @@ AC_CHECK_FUNCS([mkfifo], [HAVE_MKFIFO=1], [HAVE_MKFIFO=0]) AM_CONDITIONAL(HAVE_MKFIFO, test "x$HAVE_MKFIFO" = "x1") # X/OPEN -AC_CHECK_FUNCS([readlink]) +AC_CHECK_FUNCS_ONCE([readlink]) # SUSv2 -AC_CHECK_FUNCS([ctime_r usleep]) +AC_CHECK_FUNCS_ONCE([ctime_r usleep]) # SUSv3 -AC_CHECK_FUNCS([strerror_r]) +AC_CHECK_FUNCS_ONCE([strerror_r]) # BSD -AC_CHECK_FUNCS([lstat]) +AC_CHECK_FUNCS_ONCE([lstat]) # Non-standard -AC_CHECK_FUNCS([setresuid setresgid setreuid setregid seteuid setegid ppoll strsignal sig2str strtof_l]) +AC_CHECK_FUNCS_ONCE([setresuid setresgid setreuid setregid seteuid setegid ppoll strsignal sig2str strtof_l]) AC_FUNC_ALLOCA @@ -458,7 +456,7 @@ AC_DEFINE_UNQUOTED(PA_CFLAGS,"$CFLAGS", [The CFLAGS used during compilation]) AC_SYS_LARGEFILE # Check for open64 to know if the current system does have open64() and similar functions -AC_CHECK_FUNCS([open64]) +AC_CHECK_FUNCS_ONCE([open64]) #### [lib]iconv #### @@ -537,11 +535,11 @@ fi #### Valgrind (optional) #### -AC_CHECK_HEADERS([valgrind/memcheck.h]) +AC_CHECK_HEADERS_ONCE([valgrind/memcheck.h]) #### Sound file #### -PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.10 ]) +PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.20 ]) AC_SUBST(LIBSNDFILE_CFLAGS) AC_SUBST(LIBSNDFILE_LIBS) @@ -601,6 +599,65 @@ AC_SUBST(LIBSAMPLERATE_LIBS) AC_SUBST(HAVE_LIBSAMPLERATE) AM_CONDITIONAL([HAVE_LIBSAMPLERATE], [test "x$HAVE_LIBSAMPLERATE" = x1]) +#### Database support #### + +HAVE_TDB=0 +HAVE_GDBM=0 + +AC_ARG_WITH( + [database], + AS_HELP_STRING([--with-database=auto|tdb|gdbm],[Choose database backend.]),[],[with_database=auto]) + +if test "x${with_database}" = "xauto" -o "x${with_database}" = "xtdb" ; then + PKG_CHECK_MODULES(TDB, [ tdb ], + [ + HAVE_TDB=1 + with_database=tdb + ], [ + if test "x${with_database}" = "xtdb" ; then + AC_MSG_ERROR([*** tdb not found]) + fi + ]) +fi + +if test "x${with_database}" = "xauto" -o "x${with_database}" = "xgdbm" ; then + have_gdbm=yes + + AC_CHECK_LIB(gdbm, gdbm_open, [], [have_gdbm=no]) + AC_CHECK_HEADERS(gdbm.h, [], [have_gdbm=no]) + + if test "x${have_gdbm}" = "xyes" ; then + HAVE_GDBM=1 + GDBM_CFLAGS= + GDBM_LIBS=-lgdbm + with_database=gdbm + elif test "x${with_database}" = "xgdbm"; then + AC_MSG_ERROR([*** gdbm not found]) + fi +fi + +if test "x${HAVE_TDB}" != x1 -a "x${HAVE_GDBM}" != x1; then + AC_MSG_ERROR([*** missing database backend]) +fi + +if test "x${HAVE_TDB}" = x1 ; then + AC_DEFINE([HAVE_TDB], 1, [Have tdb?]) +fi + +if test "x${HAVE_GDBM}" = x1 ; then + AC_DEFINE([HAVE_GDBM], 1, [Have gdbm?]) +fi + +AC_SUBST(TDB_CFLAGS) +AC_SUBST(TDB_LIBS) +AC_SUBST(HAVE_TDB) +AM_CONDITIONAL([HAVE_TDB], [test "x$HAVE_TDB" = x1]) + +AC_SUBST(GDBM_CFLAGS) +AC_SUBST(GDBM_LIBS) +AC_SUBST(HAVE_GDBM) +AM_CONDITIONAL([HAVE_GDBM], [test "x$HAVE_GDBM" = x1]) + #### OSS support (optional) #### AC_ARG_ENABLE([oss], @@ -1322,7 +1379,6 @@ po/Makefile.in SHAVE_INIT AC_OUTPUT -SHAVE_OUTPUT # ========================================================================== ENABLE_X11=no @@ -1410,6 +1466,16 @@ if test "x${HAVE_POLKIT}" = "x1" ; then ENABLE_POLKIT=yes fi +ENABLE_GDBM=no +if test "x${HAVE_GDBM}" = "x1" ; then + ENABLE_GDBM=yes +fi + +ENABLE_TDB=no +if test "x${HAVE_TDB}" = "x1" ; then + ENABLE_TDB=yes +fi + ENABLE_OPENSSL=no if test "x${HAVE_OPENSSL}" = "x1" ; then ENABLE_OPENSSL=yes @@ -1456,6 +1522,8 @@ echo " Enable PolicyKit: ${ENABLE_POLKIT} Enable IPv6: ${ENABLE_IPV6} Enable OpenSSL (for Airtunes): ${ENABLE_OPENSSL} + Enable tdb: ${ENABLE_TDB} + Enable gdbm: ${ENABLE_GDBM} System User: ${PA_SYSTEM_USER} System Group: ${PA_SYSTEM_GROUP} diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 00000000..710870cd --- /dev/null +++ b/git-version-gen @@ -0,0 +1,152 @@ +#!/bin/sh +# Print a version string. +scriptversion=2008-04-08.07 + +# Copyright (C) 2007-2008 Free Software Foundation +# +# 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 3, 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 Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif test -d .git \ + && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/m4/shave.m4 b/m4/shave.m4 index 0c2c9f5a..0a3509e5 100644 --- a/m4/shave.m4 +++ b/m4/shave.m4 @@ -1,4 +1,5 @@ dnl Make automake/libtool output more friendly to humans +dnl Damien Lespiau <damien.lespiau@gmail.com> dnl dnl SHAVE_INIT([shavedir],[default_mode]) dnl @@ -53,14 +54,17 @@ AC_DEFUN([SHAVE_INIT], SHAVE_SAVED_CXX=$CXX SHAVE_SAVED_FC=$FC SHAVE_SAVED_F77=$F77 + SHAVE_SAVED_OBJC=$OBJC CC="${SHELL} ${shavedir}/shave cc ${SHAVE_SAVED_CC}" CXX="${SHELL} ${shavedir}/shave cxx ${SHAVE_SAVED_CXX}" FC="${SHELL} ${shavedir}/shave fc ${SHAVE_SAVED_FC}" F77="${SHELL} ${shavedir}/shave f77 ${SHAVE_SAVED_F77}" + OBJC="${SHELL} ${shavedir}/shave objc ${SHAVE_SAVED_OBJC}" AC_SUBST(CC) AC_SUBST(CXX) AC_SUBST(FC) AC_SUBST(F77) + AC_SUBST(OBJC) V=@ else diff --git a/man/pulse-daemon.conf.5.xml.in b/man/pulse-daemon.conf.5.xml.in index afa7ca00..68bcb77f 100644 --- a/man/pulse-daemon.conf.5.xml.in +++ b/man/pulse-daemon.conf.5.xml.in @@ -164,6 +164,14 @@ USA. </option> <option> + <p><opt>lock-memory=</opt> Locks the entire PulseAudio process + into memory. While this might increase drop-out safety when used + in conjunction with real-time scheduling this takes away a lot + of memory from other processes and might hence considerably slow + down your system. Defaults to <opt>no</opt>.</p> + </option> + + <option> <p><opt>flat-volumes=</opt> Enable 'flat' volumes, i.e. where possible let the sink volume equal the maximum of the volumes of the inputs connected to it. Takes a boolean argument, defaults @@ -228,13 +236,6 @@ USA. </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 @@ -267,9 +268,9 @@ USA. </option> <option> - <p><opt>default-script-file=</opt> Load the default + <p><opt>load-default-script-file=</opt> Load the default configuration script file as specified - in <opt>default-script-file=</opt>. Defaults to "yes".</p> + in <opt>default-script-file=</opt>. Defaults to <opt>yes</opt>.</p> </option> </section> @@ -296,6 +297,23 @@ USA. might alter this setting.</p> </option> + <option> + <p><opt>log-meta=</opt> With each logged message log the code + location the message was generated from. Defaults to + <opt>no</opt>.</p> + </option> + + <option> + <p><opt>log-time=</opt> With each logged messages log the + relative time since startup. Defaults to <opt>no</opt>.</p> + </option> + + <option> + <p><opt>log-backtrace=</opt> When greater than 0, with each + logged message log a code stack trace up the the specified + number of stack frames. Defaults to <opt>0</opt>.</p> + </option> + </section> <section name="Resource Limits"> @@ -371,11 +389,13 @@ USA. <option> <p><opt>default-sample-format=</opt> The default sampling format. Specify one of <opt>u8</opt>, <opt>s16le</opt>, - <opt>s16be</opt>, <opt>s32le</opt>, - <opt>s32be</opt>, <opt>float32le</opt>, <opt>float32be</opt>, + <opt>s16be</opt>, <opt>s24le</opt>, <opt>s24be</opt>, + <opt>s24-32le</opt>, <opt>s24-32be</opt>, <opt>s32le</opt>, + <opt>s32be</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>s32ne</opt>, <opt>s32re</opt>, + the CPU the formats <opt>s16ne</opt>, <opt>s16re</opt>, + <opt>s24ne</opt>, <opt>s24re</opt>, <opt>s24-32ne</opt>, + <opt>s24-32re</opt>, <opt>s32ne</opt>, <opt>s32re</opt>, <opt>float32ne</opt>, <opt>float32re</opt> (for native, resp. reverse endian) are available as aliases.</p> </option> @@ -401,7 +421,8 @@ USA. 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> + setting supported. Modern drivers that support timer-based + scheduling ignore these options.</p> <option> <p><opt>default-fragments=</opt> The default number of @@ -56,6 +56,9 @@ link,*) *,f77) Q=" F77 " ;; +*,objc) + Q=" OBJC " + ;; *,*) # should not happen Q=" CC " diff --git a/src/Makefile.am b/src/Makefile.am index b6db8154..38395e7a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -56,6 +56,8 @@ AM_CFLAGS = \ -I$(top_builddir)/src/modules/gconf \ -I$(top_srcdir)/src/modules/bluetooth \ -I$(top_builddir)/src/modules/bluetooth \ + -I$(top_srcdir)/src/modules/oss \ + -I$(top_builddir)/src/modules/oss \ -I$(top_srcdir)/src/modules/alsa \ -I$(top_builddir)/src/modules/alsa \ -I$(top_srcdir)/src/modules/raop \ @@ -175,7 +177,6 @@ endif bin_PROGRAMS += \ pacat \ pactl \ - paplay \ pasuspender if HAVE_AF_UNIX @@ -193,23 +194,18 @@ endif bin_SCRIPTS = esdcompat start-pulseaudio-x11 pacat_SOURCES = utils/pacat.c -pacat_LDADD = $(AM_LDADD) libpulse.la -pacat_CFLAGS = $(AM_CFLAGS) +pacat_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) +pacat_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) pacat_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -paplay_SOURCES = utils/paplay.c -paplay_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS) -paplay_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) -paplay_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) - pactl_SOURCES = utils/pactl.c pactl_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) pactl_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) pactl_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) pasuspender_SOURCES = utils/pasuspender.c -pasuspender_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) -pasuspender_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) +pasuspender_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la +pasuspender_CFLAGS = $(AM_CFLAGS) pasuspender_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) pacmd_SOURCES = utils/pacmd.c @@ -223,7 +219,7 @@ pax11publish_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@ pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) pabrowse_SOURCES = utils/pabrowse.c -pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la +pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la libpulsecommon-@PA_MAJORMINORMICRO@.la pabrowse_CFLAGS = $(AM_CFLAGS) pabrowse_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) @@ -583,6 +579,7 @@ libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/poll.c pulsecore/poll.h \ pulsecore/prioq.c pulsecore/prioq.h \ pulsecore/memtrap.c pulsecore/memtrap.h \ + pulsecore/aupdate.c pulsecore/aupdate.h \ pulsecore/proplist-util.c pulsecore/proplist-util.h \ pulsecore/pstream-util.c pulsecore/pstream-util.h \ pulsecore/pstream.c pulsecore/pstream.h \ @@ -600,11 +597,12 @@ libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/tagstruct.c pulsecore/tagstruct.h \ pulsecore/time-smoother.c pulsecore/time-smoother.h \ pulsecore/tokenizer.c pulsecore/tokenizer.h \ + pulsecore/sndfile-util.c pulsecore/sndfile-util.h \ pulsecore/winsock.h libpulsecommon_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) libpulsecommon_@PA_MAJORMINORMICRO@_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version -libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) +libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS) # proplist-util.h uses these header files, but not the library itself! libpulsecommon_@PA_MAJORMINORMICRO@_la_CFLAGS += $(GLIB20_CFLAGS) $(GTK20_CFLAGS) @@ -815,11 +813,12 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/source.c pulsecore/source.h \ pulsecore/start-child.c pulsecore/start-child.h \ pulsecore/thread-mq.c pulsecore/thread-mq.h \ - pulsecore/time-smoother.c pulsecore/time-smoother.h + pulsecore/time-smoother.c pulsecore/time-smoother.h \ + pulsecore/database.h -libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(WINSOCK_CFLAGS) $(LIBOIL_CFLAGS) libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS = -avoid-version -libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(LIBSPEEX_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libpulsecore-foreign.la +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libpulsecore-foreign.la if HAVE_X11 libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/x11wrap.c pulsecore/x11wrap.h @@ -827,13 +826,25 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(X11_CFLAGS) libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS += $(X11_LIBS) endif - if HAVE_DBUS libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/dbus-shared.c pulsecore/dbus-shared.h libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(DBUS_CFLAGS) libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(DBUS_LIBS) endif +if HAVE_GDBM +libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/database-gdbm.c +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(GDBM_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(GDBM_LIBS) +endif + +if HAVE_TDB +libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/database-tdb.c +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(TDB_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(TDB_LIBS) +endif + + # We split the foreign code off to not be annoyed by warnings we don't care about noinst_LTLIBRARIES = libpulsecore-foreign.la @@ -882,7 +893,7 @@ libprotocol_cli_la_SOURCES = pulsecore/protocol-cli.c pulsecore/protocol-cli.h libprotocol_cli_la_LDFLAGS = -avoid-version libprotocol_cli_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libcli.la -libprotocol_http_la_SOURCES = pulsecore/protocol-http.c pulsecore/protocol-http.h +libprotocol_http_la_SOURCES = pulsecore/protocol-http.c pulsecore/protocol-http.h pulsecore/mime-type.c pulsecore/mime-type.h libprotocol_http_la_LDFLAGS = -avoid-version libprotocol_http_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la @@ -1059,6 +1070,16 @@ modlibexec_LTLIBRARIES += \ module-hal-detect.la endif +if HAVE_UDEV +modlibexec_LTLIBRARIES += \ + module-udev-detect.la +endif + +if HAVE_DBUS +modlibexec_LTLIBRARIES += \ + module-rygel-media-server.la +endif + if HAVE_BLUEZ modlibexec_LTLIBRARIES += \ libbluetooth-util.la \ @@ -1114,10 +1135,11 @@ SYMDEF_FILES = \ 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-x11-cork-request-symdef.h \ + modules/module-rygel-media-server-symdef.h \ + modules/x11/module-x11-bell-symdef.h \ + modules/x11/module-x11-publish-symdef.h \ + modules/x11/module-x11-xsmp-symdef.h \ + modules/x11/module-x11-cork-request-symdef.h \ modules/oss/module-oss-symdef.h \ modules/alsa/module-alsa-sink-symdef.h \ modules/alsa/module-alsa-source-symdef.h \ @@ -1127,8 +1149,8 @@ SYMDEF_FILES = \ modules/module-detect-symdef.h \ modules/rtp/module-rtp-send-symdef.h \ modules/rtp/module-rtp-recv-symdef.h \ - modules/module-jack-sink-symdef.h \ - modules/module-jack-source-symdef.h \ + modules/jack/module-jack-sink-symdef.h \ + modules/jack/module-jack-source-symdef.h \ modules/module-volume-restore-symdef.h \ modules/module-device-restore-symdef.h \ modules/module-stream-restore-symdef.h \ @@ -1138,6 +1160,7 @@ SYMDEF_FILES = \ modules/module-rescue-streams-symdef.h \ modules/module-suspend-on-idle-symdef.h \ modules/module-hal-detect-symdef.h \ + modules/module-udev-detect-symdef.h \ modules/bluetooth/module-bluetooth-proximity-symdef.h \ modules/bluetooth/module-bluetooth-discover-symdef.h \ modules/bluetooth/module-bluetooth-device-symdef.h \ @@ -1291,22 +1314,22 @@ module_tunnel_source_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@. # X11 -module_x11_bell_la_SOURCES = modules/module-x11-bell.c +module_x11_bell_la_SOURCES = modules/x11/module-x11-bell.c module_x11_bell_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS) module_x11_bell_la_LDFLAGS = $(MODULE_LDFLAGS) module_x11_bell_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_x11_publish_la_SOURCES = modules/module-x11-publish.c +module_x11_publish_la_SOURCES = modules/x11/module-x11-publish.c module_x11_publish_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS) module_x11_publish_la_LDFLAGS = $(MODULE_LDFLAGS) module_x11_publish_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_x11_xsmp_la_SOURCES = modules/module-x11-xsmp.c +module_x11_xsmp_la_SOURCES = modules/x11/module-x11-xsmp.c module_x11_xsmp_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS) module_x11_xsmp_la_LDFLAGS = $(MODULE_LDFLAGS) module_x11_xsmp_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_x11_cork_request_la_SOURCES = modules/module-x11-cork-request.c +module_x11_cork_request_la_SOURCES = modules/x11/module-x11-cork-request.c module_x11_cork_request_la_CFLAGS = $(AM_CFLAGS) $(X11_CFLAGS) module_x11_cork_request_la_LDFLAGS = $(MODULE_LDFLAGS) module_x11_cork_request_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la @@ -1341,7 +1364,7 @@ libalsa_util_la_CFLAGS += $(UDEV_CFLAGS) endif if HAVE_DBUS -libalsa_util_la_SOURCES += modules/reserve.h modules/reserve.c +libalsa_util_la_SOURCES += modules/reserve.h modules/reserve.c modules/reserve-monitor.h modules/reserve-monitor.c libalsa_util_la_LIBADD += $(DBUS_LIBS) libalsa_util_la_CFLAGS += $(DBUS_CFLAGS) endif @@ -1434,19 +1457,19 @@ module_cork_music_on_phone_la_CFLAGS = $(AM_CFLAGS) # Device volume/muted restore module module_device_restore_la_SOURCES = modules/module-device-restore.c module_device_restore_la_LDFLAGS = $(MODULE_LDFLAGS) -module_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la -lgdbm libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_device_restore_la_CFLAGS = $(AM_CFLAGS) # Stream volume/muted/device restore module module_stream_restore_la_SOURCES = modules/module-stream-restore.c module_stream_restore_la_LDFLAGS = $(MODULE_LDFLAGS) -module_stream_restore_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la -lgdbm libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_stream_restore_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_stream_restore_la_CFLAGS = $(AM_CFLAGS) # Card profile restore module module_card_restore_la_SOURCES = modules/module-card-restore.c module_card_restore_la_LDFLAGS = $(MODULE_LDFLAGS) -module_card_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la -lgdbm libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_card_restore_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_card_restore_la_CFLAGS = $(AM_CFLAGS) # Default sink/source restore module @@ -1486,12 +1509,12 @@ module_rtp_recv_la_CFLAGS = $(AM_CFLAGS) # JACK -module_jack_sink_la_SOURCES = modules/module-jack-sink.c +module_jack_sink_la_SOURCES = modules/jack/module-jack-sink.c module_jack_sink_la_LDFLAGS = $(MODULE_LDFLAGS) module_jack_sink_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la $(JACK_LIBS) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_jack_sink_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS) -module_jack_source_la_SOURCES = modules/module-jack-source.c +module_jack_source_la_SOURCES = modules/jack/module-jack-source.c module_jack_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_jack_source_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la $(JACK_LIBS) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_jack_source_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS) @@ -1501,6 +1524,11 @@ module_hal_detect_la_LDFLAGS = $(MODULE_LDFLAGS) module_hal_detect_la_LIBADD = $(AM_LIBADD) $(HAL_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_hal_detect_la_CFLAGS = $(AM_CFLAGS) $(HAL_CFLAGS) +module_udev_detect_la_SOURCES = modules/module-udev-detect.c +module_udev_detect_la_LDFLAGS = $(MODULE_LDFLAGS) +module_udev_detect_la_LIBADD = $(AM_LIBADD) $(UDEV_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la +module_udev_detect_la_CFLAGS = $(AM_CFLAGS) $(UDEV_CFLAGS) + module_console_kit_la_SOURCES = modules/module-console-kit.c module_console_kit_la_LDFLAGS = $(MODULE_LDFLAGS) module_console_kit_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la @@ -1566,6 +1594,11 @@ module_raop_discover_la_LDFLAGS = $(MODULE_LDFLAGS) module_raop_discover_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_raop_discover_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) +# Rygel +module_rygel_media_server_la_SOURCES = modules/module-rygel-media-server.c +module_rygel_media_server_la_LDFLAGS = $(MODULE_LDFLAGS) +module_rygel_media_server_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libprotocol-http.la +module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) ################################### # Some minor stuff # @@ -1616,11 +1649,17 @@ install-exec-hook: chmod u+s $(DESTDIR)$(bindir)/pulseaudio -chmod u+s $(DESTDIR)$(pulselibexecdir)/proximity-helper ln -sf pacat $(DESTDIR)$(bindir)/parec + ln -sf pacat $(DESTDIR)$(bindir)/pamon + ln -sf pacat $(DESTDIR)$(bindir)/paplay + ln -sf pacat $(DESTDIR)$(bindir)/parecord rm -f $(DESTDIR)$(libdir)/libpulsedsp.la rm -f $(DESTDIR)$(modlibexecdir)/*.la uninstall-hook: rm -f $(DESTDIR)$(bindir)/parec + rm -f $(DESTDIR)$(bindir)/pamon + rm -f $(DESTDIR)$(bindir)/paplay + rm -f $(DESTDIR)$(bindir)/parecord rm -f $(DESTDIR)$(libdir)/libpulsedsp.* rm -f $(DESTDIR)$(modlibexecdir)/*.so @@ -1642,7 +1681,7 @@ update-sbc: done update-reserve: - for i in reserve.c reserve.h ; do \ + for i in reserve.c reserve.h reserve-monitor.c reserve-monitor.h ; do \ wget -O modules/$$i http://git.0pointer.de/\?p=reserve.git\;a=blob_plain\;f=$$i\;hb=master ; \ done diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c index d78089e1..ecb38486 100644 --- a/src/daemon/cmdline.c +++ b/src/daemon/cmdline.c @@ -31,6 +31,7 @@ #include <pulse/xmalloc.h> #include <pulse/i18n.h> +#include <pulse/util.h> #include <pulsecore/core-util.h> #include <pulsecore/strbuf.h> @@ -109,15 +110,8 @@ static const struct option long_options[] = { }; 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" @@ -172,7 +166,8 @@ void pa_cmdline_help(const char *argv0) { " -C Open a command line on the running TTY\n" " after startup\n\n" - " -n Don't load default script file\n"), e); + " -n Don't load default script file\n"), + pa_path_get_filename(argv0)); } int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d) { diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index ac6cc8aa..664e4fde 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -85,6 +85,7 @@ static const pa_daemon_conf default_conf = { .system_instance = FALSE, .no_cpu_limit = FALSE, .disable_shm = FALSE, + .lock_memory = FALSE, .default_n_fragments = 4, .default_fragment_size_msec = 25, .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 }, @@ -446,6 +447,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { { "no-cpu-limit", pa_config_parse_bool, &c->no_cpu_limit, NULL }, { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, { "flat-volumes", pa_config_parse_bool, &c->flat_volumes, NULL }, + { "lock-memory", pa_config_parse_bool, &c->lock_memory, NULL }, { "exit-idle-time", pa_config_parse_int, &c->exit_idle_time, NULL }, { "scache-idle-time", pa_config_parse_int, &c->scache_idle_time, NULL }, { "realtime-priority", parse_rtprio, c, NULL }, @@ -595,16 +597,14 @@ FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c) { return f; } - -static const char* const log_level_to_string[] = { - [PA_LOG_DEBUG] = "debug", - [PA_LOG_INFO] = "info", - [PA_LOG_NOTICE] = "notice", - [PA_LOG_WARN] = "warning", - [PA_LOG_ERROR] = "error" -}; - char *pa_daemon_conf_dump(pa_daemon_conf *c) { + static const char* const log_level_to_string[] = { + [PA_LOG_DEBUG] = "debug", + [PA_LOG_INFO] = "info", + [PA_LOG_NOTICE] = "notice", + [PA_LOG_WARN] = "warning", + [PA_LOG_ERROR] = "error" + }; pa_strbuf *s; char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; @@ -630,6 +630,7 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) { 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, "flat-volumes = %s\n", pa_yes_no(c->flat_volumes)); + pa_strbuf_printf(s, "lock-memory = %s\n", pa_yes_no(c->lock_memory)); pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time); pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time); pa_strbuf_printf(s, "dl-search-path = %s\n", pa_strempty(c->dl_search_path)); diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index 9cec189f..dd69e048 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -73,7 +73,8 @@ typedef struct pa_daemon_conf { disallow_exit, log_meta, log_time, - flat_volumes; + flat_volumes, + lock_memory; int exit_idle_time, scache_idle_time, auto_log_target, diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in index fcd2513a..d119716d 100644 --- a/src/daemon/daemon.conf.in +++ b/src/daemon/daemon.conf.in @@ -27,6 +27,8 @@ ; system-instance = no ; disable-shm = no ; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB +; lock-memory = no +; no-cpu-limit = no ; high-priority = yes ; nice-level = -11 @@ -35,7 +37,6 @@ ; realtime-priority = 5 ; exit-idle-time = 20 -; module-idle-time = 20 ; scache-idle-time = 20 ; dl-search-path = (depends on architecture) @@ -55,8 +56,6 @@ ; flat-volumes = yes -; no-cpu-limit = no - ; rlimit-fsize = -1 ; rlimit-data = -1 ; rlimit-stack = -1 diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in index fa0683e1..a35ff8ff 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -49,11 +49,11 @@ load-module module-augment-properties #load-module module-pipe-sink ### Automatically load driver modules depending on the hardware available -.ifexists module-hal-detect@PA_SOEXT@ -load-module module-hal-detect +.ifexists module-udev-detect@PA_SOEXT@ +load-module module-udev-detect .else ### Alternatively use the static hardware detection module (for systems that -### lack HAL support) +### lack udev support) load-module module-detect .endif diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c index 0ffc0fc0..92470b49 100644 --- a/src/daemon/dumpmodules.c +++ b/src/daemon/dumpmodules.c @@ -71,6 +71,8 @@ static void long_info(const char *name, const char *path, pa_modinfo *i) { if (i->usage) printf(_("Usage: %s\n"), i->usage); printf(_("Load Once: %s\n"), pa_yes_no(i->load_once)); + if (i->deprecated) + printf(_("DEPRECATION WARNING: %s\n"), i->deprecated); } if (path) diff --git a/src/daemon/main.c b/src/daemon/main.c index a2324516..58f8d660 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -40,6 +40,10 @@ #include <liboil/liboil.h> +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> #endif @@ -946,9 +950,7 @@ int main(int argc, char *argv[]) { valid_pid_file = TRUE; } -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif + pa_disable_sigpipe(); if (pa_rtclock_hrtimer()) pa_log_info(_("Fresh high-resolution timers available! Bon appetit!")); @@ -962,6 +964,17 @@ int main(int argc, char *argv[]) { pa_rtsig_configure(SIGRTMIN, SIGRTMAX-1); #endif + if (conf->lock_memory) { +#ifdef HAVE_SYS_MMAN_H + if (mlockall(MCL_FUTURE) < 0) + pa_log_warn("mlockall() failed: %s", pa_cstrerror(errno)); + else + pa_log_info("Sucessfully locked process into memory."); +#else + pa_log_warn("Memory locking requested but not supported on platform."); +#endif + } + pa_memtrap_install(); pa_assert_se(mainloop = pa_mainloop_new()); diff --git a/src/daemon/start-pulseaudio-x11.in b/src/daemon/start-pulseaudio-x11.in index 391a6d3c..c57c489d 100755 --- a/src/daemon/start-pulseaudio-x11.in +++ b/src/daemon/start-pulseaudio-x11.in @@ -19,6 +19,8 @@ set -e +[ -z "$PULSE_SERVER" ] + @PA_BINARY@ --start "$@" if [ x"$DISPLAY" != x ] ; then diff --git a/src/map-file b/src/map-file index d0102ae0..c46c6792 100644 --- a/src/map-file +++ b/src/map-file @@ -15,11 +15,13 @@ pa_channel_map_can_balance; pa_channel_map_can_fade; pa_channel_map_compatible; pa_channel_map_equal; +pa_channel_map_has_position; 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_mask; pa_channel_map_parse; pa_channel_map_snprint; pa_channel_map_superset; @@ -110,19 +112,24 @@ pa_context_suspend_source_by_name; pa_context_unload_module; pa_context_unref; pa_cvolume_avg; +pa_cvolume_avg_mask; pa_cvolume_channels_equal_to; pa_cvolume_compatible; pa_cvolume_compatible_with_channel_map; pa_cvolume_equal; pa_cvolume_get_balance; pa_cvolume_get_fade; +pa_cvolume_get_position; pa_cvolume_init; pa_cvolume_max; +pa_cvolume_max_mask; pa_cvolume_remap; pa_cvolume_scale; +pa_cvolume_scale_mask; pa_cvolume_set; pa_cvolume_set_balance; pa_cvolume_set_fade; +pa_cvolume_set_position; pa_cvolume_snprint; pa_cvolume_valid; pa_ext_stream_restore_delete; @@ -175,6 +182,7 @@ pa_proplist_iterate; pa_proplist_new; pa_proplist_set; pa_proplist_setf; +pa_proplist_setp; pa_proplist_sets; pa_proplist_size; pa_proplist_to_string; @@ -182,6 +190,8 @@ pa_proplist_to_string_sep; pa_proplist_unset; pa_proplist_unset_many; pa_proplist_update; +pa_sample_format_is_be; +pa_sample_format_is_le; pa_sample_format_to_string; pa_sample_size; pa_sample_size_of_format; diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 41e8b477..59a5ca75 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -116,6 +116,8 @@ struct userdata { pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; + pa_reserve_monitor_wrapper *monitor; + pa_hook_slot *monitor_slot; }; static void userdata_free(struct userdata *u); @@ -124,7 +126,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u pa_assert(r); pa_assert(u); - if (pa_sink_suspend(u->sink, TRUE) < 0) + if (pa_sink_suspend(u->sink, TRUE, PA_SUSPEND_APPLICATION) < 0) return PA_HOOK_CANCEL; return PA_HOOK_OK; @@ -185,6 +187,57 @@ static int reserve_init(struct userdata *u, const char *dname) { return 0; } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { + pa_bool_t b; + + pa_assert(w); + pa_assert(u); + + b = PA_PTR_TO_UINT(busy) && !u->reserve; + + pa_sink_suspend(u->sink, b, PA_SUSPEND_APPLICATION); + return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { + pa_assert(u); + + if (u->monitor_slot) { + pa_hook_slot_free(u->monitor_slot); + u->monitor_slot = NULL; + } + + if (u->monitor) { + pa_reserve_monitor_wrapper_unref(u->monitor); + u->monitor = NULL; + } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { + char *rname; + + pa_assert(u); + pa_assert(dname); + + if (pa_in_system_mode()) + return 0; + + /* We are resuming, try to lock the device */ + if (!(rname = pa_alsa_get_reserve_name(dname))) + return 0; + + u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); + pa_xfree(rname); + + if (!(u->monitor)) + return -1; + + pa_assert(!u->monitor_slot); + u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + + return 0; +} + static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; @@ -473,7 +526,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle u->since_start += frames * u->frame_size; #ifdef DEBUG_TIMING - pa_log_debug("Wrote %lu bytes", (unsigned long) (frames * u->frame_size)); + pa_log_debug("Wrote %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes); #endif if ((size_t) frames * u->frame_size >= n_bytes) @@ -1146,7 +1199,7 @@ fail: static void sink_get_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; - int err, sw; + int err, sw = 0; pa_assert(u); pa_assert(u->mixer_elem); @@ -1508,6 +1561,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca size_t frame_size; pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; pa_sink_new_data data; + char *control_device = NULL; pa_assert(m); pa_assert(ma); @@ -1579,9 +1633,14 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_rtclock_usec(), TRUE); - if (reserve_init(u, pa_modargs_get_value( - ma, "device_id", - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) + dev_id = pa_modargs_get_value( + ma, "device_id", + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + if (reserve_init(u, dev_id) < 0) + goto fail; + + if (reserve_monitor_init(u, dev_id) < 0) goto fail; b = use_mmap; @@ -1664,7 +1723,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - pa_alsa_find_mixer_and_elem(u->pcm_handle, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); + pa_alsa_find_mixer_and_elem(u->pcm_handle, &control_device, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); pa_sink_new_data_init(&data); data.driver = driver; @@ -1687,6 +1746,17 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_alsa_init_description(data.proplist); + if (control_device) { + pa_alsa_init_proplist_ctl(data.proplist, control_device); + pa_xfree(control_device); + } + + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY|(u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0)); pa_sink_new_data_done(&data); @@ -1730,7 +1800,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_log_info("Time scheduling watermark is %0.2fms", (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); } else - u->sink->fixed_latency = pa_bytes_to_usec(u->hwbuf_size, &ss); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss)); reserve_update(u); @@ -1770,7 +1840,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca fail: - userdata_free(u); + if (u) + userdata_free(u); return NULL; } @@ -1815,6 +1886,7 @@ static void userdata_free(struct userdata *u) { pa_smoother_free(u->smoother); reserve_done(u); + monitor_done(u); pa_xfree(u->device_name); pa_xfree(u); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 843f70bb..c176309e 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -114,6 +114,8 @@ struct userdata { pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; + pa_reserve_monitor_wrapper *monitor; + pa_hook_slot *monitor_slot; }; static void userdata_free(struct userdata *u); @@ -122,7 +124,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u pa_assert(r); pa_assert(u); - if (pa_source_suspend(u->source, TRUE) < 0) + if (pa_source_suspend(u->source, TRUE, PA_SUSPEND_APPLICATION) < 0) return PA_HOOK_CANCEL; return PA_HOOK_OK; @@ -183,6 +185,57 @@ static int reserve_init(struct userdata *u, const char *dname) { return 0; } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { + pa_bool_t b; + + pa_assert(w); + pa_assert(u); + + b = PA_PTR_TO_UINT(busy) && !u->reserve; + + pa_source_suspend(u->source, b, PA_SUSPEND_APPLICATION); + return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { + pa_assert(u); + + if (u->monitor_slot) { + pa_hook_slot_free(u->monitor_slot); + u->monitor_slot = NULL; + } + + if (u->monitor) { + pa_reserve_monitor_wrapper_unref(u->monitor); + u->monitor = NULL; + } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { + char *rname; + + pa_assert(u); + pa_assert(dname); + + if (pa_in_system_mode()) + return 0; + + /* We are resuming, try to lock the device */ + if (!(rname = pa_alsa_get_reserve_name(dname))) + return 0; + + u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); + pa_xfree(rname); + + if (!(u->monitor)) + return -1; + + pa_assert(!u->monitor_slot); + u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + + return 0; +} + static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; pa_assert(u); @@ -455,7 +508,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled u->read_count += frames * u->frame_size; #ifdef DEBUG_TIMING - pa_log_debug("Read %lu bytes", (unsigned long) (frames * u->frame_size)); + pa_log_debug("Read %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes); #endif if ((size_t) frames * u->frame_size >= n_bytes) @@ -1105,7 +1158,7 @@ fail: static void source_get_mute_cb(pa_source *s) { struct userdata *u = s->userdata; - int err, sw; + int err, sw = 0; pa_assert(u); pa_assert(u->mixer_elem); @@ -1366,6 +1419,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p size_t frame_size; pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; pa_source_new_data data; + char *control_device = NULL; pa_assert(m); pa_assert(ma); @@ -1437,9 +1491,14 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_rtclock_usec(), FALSE); - if (reserve_init(u, pa_modargs_get_value( - ma, "device_id", - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) + dev_id = pa_modargs_get_value( + ma, "device_id", + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + if (reserve_init(u, dev_id) < 0) + goto fail; + + if (reserve_monitor_init(u, dev_id) < 0) goto fail; b = use_mmap; @@ -1519,7 +1578,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - pa_alsa_find_mixer_and_elem(u->pcm_handle, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); + pa_alsa_find_mixer_and_elem(u->pcm_handle, &control_device, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); pa_source_new_data_init(&data); data.driver = driver; @@ -1542,6 +1601,17 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_alsa_init_description(data.proplist); + if (control_device) { + pa_alsa_init_proplist_ctl(data.proplist, control_device); + pa_xfree(control_device); + } + + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|(u->use_tsched ? PA_SOURCE_DYNAMIC_LATENCY : 0)); pa_source_new_data_done(&data); @@ -1582,7 +1652,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_log_info("Time scheduling watermark is %0.2fms", (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); } else - u->source->fixed_latency = pa_bytes_to_usec(u->hwbuf_size, &ss); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->hwbuf_size, &ss)); reserve_update(u); @@ -1621,7 +1691,8 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p fail: - userdata_free(u); + if (u) + userdata_free(u); return NULL; } @@ -1663,6 +1734,7 @@ static void userdata_free(struct userdata *u) { pa_smoother_free(u->smoother); reserve_done(u); + monitor_done(u); pa_xfree(u->device_name); pa_xfree(u); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index 18d6880e..c03866cc 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -189,15 +189,6 @@ struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { fdl = pa_xnew0(struct pa_alsa_fdlist, 1); - fdl->num_fds = 0; - fdl->fds = NULL; - fdl->work_fds = NULL; - fdl->mixer = NULL; - fdl->m = NULL; - fdl->defer = NULL; - fdl->ios = NULL; - fdl->polled = FALSE; - return fdl; } @@ -281,6 +272,11 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; + pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", + snd_pcm_format_description(format_trans[*f]), + pa_alsa_strerror(ret)); + + if (*f == PA_SAMPLE_FLOAT32BE) *f = PA_SAMPLE_FLOAT32LE; else if (*f == PA_SAMPLE_FLOAT32LE) @@ -307,6 +303,10 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; + pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", + snd_pcm_format_description(format_trans[*f]), + pa_alsa_strerror(ret)); + try_auto: for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) { @@ -314,6 +314,10 @@ try_auto: if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; + + pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", + snd_pcm_format_description(format_trans[*f]), + pa_alsa_strerror(ret)); } return -1; @@ -334,7 +338,6 @@ int pa_alsa_set_hw_params( int ret = -1; snd_pcm_uframes_t _period_size = period_size ? *period_size : 0; unsigned int _periods = periods ? *periods : 0; - snd_pcm_uframes_t buffer_size; unsigned int r = ss->rate; unsigned int c = ss->channels; pa_sample_format_t f = ss->format; @@ -348,11 +351,15 @@ int pa_alsa_set_hw_params( snd_pcm_hw_params_alloca(&hwparams); - if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) + if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) { + pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret)); goto finish; + } - if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) + if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_rate_resample() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (_use_mmap) { @@ -360,14 +367,18 @@ int pa_alsa_set_hw_params( /* 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) + if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret)); goto finish; + } _use_mmap = FALSE; } - } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (!_use_mmap) _use_tsched = FALSE; @@ -375,54 +386,70 @@ int pa_alsa_set_hw_params( if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) + if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (require_exact_channel_number) { - if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) + if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_channels() failed: %s", pa_alsa_strerror(ret)); goto finish; + } } else { - if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) + if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_channels_near() failed: %s", pa_alsa_strerror(ret)); goto finish; + } } - if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) + if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_periods_integer() failed: %s", pa_alsa_strerror(ret)); goto finish; + } if (_period_size && tsched_size && _periods) { + /* 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 (_use_tsched) { - _period_size = tsched_size; - _periods = 1; + snd_pcm_uframes_t buffer_size; 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); + + _period_size = tsched_size; + _periods = 1; } - buffer_size = _periods * _period_size; + if (_period_size > 0 && _periods > 0) { + snd_pcm_uframes_t buffer_size; + + buffer_size = _periods * _period_size; + + if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) + pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret)); + } if (_periods > 0) { - /* First we pass 0 as direction to get exactly what we asked - * for. That this is necessary is presumably a bug in ALSA */ + /* First we pass 0 as direction to get exactly what we + * asked for. That this is necessary is presumably a bug + * in ALSA. All in all this is mostly a hint to ALSA, so + * we don't care if this fails. */ dir = 0; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { + if (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) { + if (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; + pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret)); } } } - - 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) @@ -528,7 +555,7 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { static const struct pa_alsa_profile_info device_table[] = { {{ 1, { PA_CHANNEL_POSITION_MONO }}, - "hw", + "hw", NULL, N_("Analog Mono"), "analog-mono", 1, @@ -536,7 +563,7 @@ static const struct pa_alsa_profile_info device_table[] = { "Capture", "Mic" }, {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "front", + "front", "hw", N_("Analog Stereo"), "analog-stereo", 10, @@ -544,7 +571,7 @@ static const struct pa_alsa_profile_info device_table[] = { "Capture", "Mic" }, {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "iec958", + "iec958", NULL, N_("Digital Stereo (IEC958)"), "iec958-stereo", 5, @@ -552,7 +579,7 @@ static const struct pa_alsa_profile_info device_table[] = { "IEC958 In", NULL }, {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "hdmi", + "hdmi", NULL, N_("Digital Stereo (HDMI)"), "hdmi-stereo", 4, @@ -561,7 +588,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, - "surround40", + "surround40", NULL, N_("Analog Surround 4.0"), "analog-surround-40", 7, @@ -570,7 +597,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, - "a52", + "a52", NULL, N_("Digital Surround 4.0 (IEC958/AC3)"), "iec958-ac3-surround-40", 2, @@ -580,7 +607,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 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", + "surround41", NULL, N_("Analog Surround 4.1"), "analog-surround-41", 7, @@ -590,7 +617,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 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", + "surround50", NULL, N_("Analog Surround 5.0"), "analog-surround-50", 7, @@ -600,7 +627,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 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", + "surround51", NULL, N_("Analog Surround 5.1"), "analog-surround-51", 8, @@ -610,7 +637,7 @@ static const struct pa_alsa_profile_info device_table[] = { {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE}}, - "a52", + "a52", NULL, N_("Digital Surround 5.1 (IEC958/AC3)"), "iec958-ac3-surround-51", 3, @@ -621,16 +648,72 @@ static const struct pa_alsa_profile_info device_table[] = { 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", + "surround71", NULL, N_("Analog Surround 7.1"), "analog-surround-71", 7, "Master", "PCM", "Capture", "Mic" }, - {{ 0, { 0 }}, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL } + {{ 0, { 0 }}, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL } }; +static snd_pcm_t *open_by_device_string_with_fallback( + const char *prefix, + const char *prefix_fallback, + 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, + pa_bool_t require_exact_channel_number) { + + snd_pcm_t *pcm_handle; + char *d; + + d = pa_sprintf_malloc("%s:%s", prefix, dev_id); + + pcm_handle = pa_alsa_open_by_device_string( + d, + dev, + ss, + map, + mode, + nfrags, + period_size, + tsched_size, + use_mmap, + use_tsched, + require_exact_channel_number); + pa_xfree(d); + + if (!pcm_handle && prefix_fallback) { + + d = pa_sprintf_malloc("%s:%s", prefix_fallback, dev_id); + + pcm_handle = pa_alsa_open_by_device_string( + d, + dev, + ss, + map, + mode, + nfrags, + period_size, + tsched_size, + use_mmap, + use_tsched, + require_exact_channel_number); + pa_xfree(d); + } + + return pcm_handle; +} + snd_pcm_t *pa_alsa_open_by_device_id_auto( const char *dev_id, char **dev, @@ -671,14 +754,14 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( pa_log_debug("Checking for %s (%s)", device_table[i].name, device_table[i].alsa_name); - d = pa_sprintf_malloc("%s:%s", device_table[i].alsa_name, dev_id); - try_ss.channels = device_table[i].map.channels; try_ss.rate = ss->rate; try_ss.format = ss->format; - pcm_handle = pa_alsa_open_by_device_string( - d, + pcm_handle = open_by_device_string_with_fallback( + device_table[i].alsa_name, + device_table[i].alsa_name_fallback, + dev_id, dev, &try_ss, map, @@ -690,8 +773,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( use_tsched, TRUE); - pa_xfree(d); - if (pcm_handle) { *ss = try_ss; @@ -703,6 +784,7 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto( return pcm_handle; } + } if (direction > 0) { @@ -775,7 +857,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile( pa_bool_t *use_tsched, const pa_alsa_profile_info *profile) { - char *d; snd_pcm_t *pcm_handle; pa_sample_spec try_ss; @@ -787,14 +868,14 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile( pa_assert(period_size); pa_assert(profile); - d = pa_sprintf_malloc("%s:%s", profile->alsa_name, dev_id); - try_ss.channels = profile->map.channels; try_ss.rate = ss->rate; try_ss.format = ss->format; - pcm_handle = pa_alsa_open_by_device_string( - d, + pcm_handle = open_by_device_string_with_fallback( + profile->alsa_name, + profile->alsa_name_fallback, + dev_id, dev, &try_ss, map, @@ -806,8 +887,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile( use_tsched, TRUE); - pa_xfree(d); - if (!pcm_handle) return NULL; @@ -860,6 +939,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( goto fail; } + pa_log_debug("Managed to open %s", d); + if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, require_exact_channel_number)) < 0) { if (!reformat) { @@ -928,26 +1009,25 @@ int pa_alsa_probe_profiles( snd_pcm_t *pcm_i = NULL; if (i->alsa_name) { - char *id; pa_sample_spec try_ss; pa_channel_map try_map; pa_log_debug("Checking for playback on %s (%s)", i->name, i->alsa_name); - id = pa_sprintf_malloc("%s:%s", i->alsa_name, dev_id); try_ss = *ss; try_ss.channels = i->map.channels; try_map = i->map; - pcm_i = pa_alsa_open_by_device_string( - id, NULL, + pcm_i = open_by_device_string_with_fallback( + i->alsa_name, + i->alsa_name_fallback, + dev_id, + NULL, &try_ss, &try_map, SND_PCM_STREAM_PLAYBACK, NULL, NULL, 0, NULL, NULL, TRUE); - pa_xfree(id); - if (!pcm_i) continue; } @@ -956,26 +1036,25 @@ int pa_alsa_probe_profiles( snd_pcm_t *pcm_j = NULL; if (j->alsa_name) { - char *jd; pa_sample_spec try_ss; pa_channel_map try_map; pa_log_debug("Checking for capture on %s (%s)", j->name, j->alsa_name); - jd = pa_sprintf_malloc("%s:%s", j->alsa_name, dev_id); try_ss = *ss; try_ss.channels = j->map.channels; try_map = j->map; - pcm_j = pa_alsa_open_by_device_string( - jd, NULL, + pcm_j = open_by_device_string_with_fallback( + j->alsa_name, + j->alsa_name_fallback, + dev_id, + NULL, &try_ss, &try_map, SND_PCM_STREAM_CAPTURE, NULL, NULL, 0, NULL, NULL, TRUE); - pa_xfree(jd); - if (!pcm_j) continue; } @@ -1117,6 +1196,7 @@ success: int pa_alsa_find_mixer_and_elem( snd_pcm_t *pcm, + char **ctl_device, snd_mixer_t **_m, snd_mixer_elem_t **_e, const char *control_name, @@ -1144,9 +1224,13 @@ int pa_alsa_find_mixer_and_elem( /* First, try by name */ if ((dev = snd_pcm_name(pcm))) - if (pa_alsa_prepare_mixer(m, dev) >= 0) + if (pa_alsa_prepare_mixer(m, dev) >= 0) { found = TRUE; + if (ctl_device) + *ctl_device = pa_xstrdup(dev); + } + /* Then, try by card index */ if (!found) { snd_pcm_info_t* info; @@ -1161,9 +1245,15 @@ int pa_alsa_find_mixer_and_elem( md = pa_sprintf_malloc("hw:%i", card_idx); if (!dev || !pa_streq(dev, md)) - if (pa_alsa_prepare_mixer(m, md) >= 0) + if (pa_alsa_prepare_mixer(m, md) >= 0) { found = TRUE; + if (ctl_device) { + *ctl_device = md; + md = NULL; + } + } + pa_xfree(md); } } @@ -1199,6 +1289,9 @@ int pa_alsa_find_mixer_and_elem( } if (!e) { + if (ctl_device) + pa_xfree(*ctl_device); + snd_mixer_close(m); return -1; } @@ -1542,6 +1635,36 @@ void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm, snd_m pa_alsa_init_proplist_pcm_info(c, p, info); } +void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { + int err; + snd_ctl_t *ctl; + snd_ctl_card_info_t *info; + const char *t; + + pa_assert(p); + + snd_ctl_card_info_alloca(&info); + + if ((err = snd_ctl_open(&ctl, name, 0)) < 0) { + pa_log_warn("Error opening low-level control device '%s'", name); + return; + } + + if ((err = snd_ctl_card_info(ctl, info)) < 0) { + pa_log_warn("Control device %s card info: %s", name, snd_strerror(err)); + snd_ctl_close(ctl); + return; + } + + if ((t = snd_ctl_card_info_get_mixername(info))) + pa_proplist_sets(p, "alsa.mixer_name", t); + + if ((t = snd_ctl_card_info_get_components(info))) + pa_proplist_sets(p, "alsa.components", t); + + snd_ctl_close(ctl); +} + int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { snd_pcm_state_t state; int err; diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 77ac8a7f..27f43712 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -56,6 +56,7 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); typedef struct pa_alsa_profile_info { pa_channel_map map; const char *alsa_name; + const char *alsa_name_fallback; const char *description; /* internationalized */ const char *name; unsigned priority; @@ -65,7 +66,9 @@ typedef struct pa_alsa_profile_info { 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, pa_bool_t playback); -int pa_alsa_find_mixer_and_elem(snd_pcm_t *pcm, snd_mixer_t **_m, snd_mixer_elem_t **_e, const char *control_name, const pa_alsa_profile_info*profile); +int pa_alsa_find_mixer_and_elem(snd_pcm_t *pcm, char **ctl_device, snd_mixer_t **_m, snd_mixer_elem_t **_e, const char *control_name, const pa_alsa_profile_info*profile); + +void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name); /* Picks a working profile based on the specified ss/map */ snd_pcm_t *pa_alsa_open_by_device_id_auto( diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 51d466e1..ad52f5e3 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -43,9 +43,12 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name for the card/sink/source, to be prefixed> " - "card_name=<name for card> " - "sink_name=<name for sink> " - "source_name=<name for source> " + "card_name=<name for the card> " + "card_properties=<properties for the card> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "source_name=<name for the source> " + "source_properties=<properties for the source> " "device_id=<ALSA card index> " "format=<sample format> " "rate=<sample rate> " @@ -61,8 +64,11 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "card_name", + "card_properties", "sink_name", + "sink_properties", "source_name", + "source_properties", "device_id", "format", "rate", @@ -135,7 +141,7 @@ static void enumerate_cb( bonus += 20000; } - pa_log_info("Found output profile '%s'", t); + pa_log_info("Found profile '%s'", t); p = pa_card_profile_new(n, t, sizeof(struct profile_data)); @@ -340,6 +346,12 @@ int pa__init(pa_module *m) { add_disabled_profile(data.profiles); + if (pa_modargs_get_proplist(ma, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_card_new_data_done(&data); + goto fail; + } + u->card = pa_card_new(m->core, &data); pa_card_new_data_done(&data); diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c index 8e600ab8..058ea205 100644 --- a/src/modules/alsa/module-alsa-sink.c +++ b/src/modules/alsa/module-alsa-sink.c @@ -40,6 +40,7 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name of the sink, to be prefixed> " "sink_name=<name for the sink> " + "sink_properities=<properties for the sink> " "device=<ALSA device> " "device_id=<ALSA card index> " "format=<sample format> " @@ -58,6 +59,7 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "sink_name", + "sink_properties", "device", "device_id", "format", diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c index e6b27b3d..3bd1b451 100644 --- a/src/modules/alsa/module-alsa-source.c +++ b/src/modules/alsa/module-alsa-source.c @@ -64,6 +64,7 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name for the source, to be prefixed> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "device=<ALSA device> " "device_id=<ALSA card index> " "format=<sample format> " @@ -82,6 +83,7 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "source_name", + "source_properties", "device", "device_id", "format", diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index ecb5e83d..dbec00d4 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -64,8 +64,11 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name for the card/sink/source, to be prefixed> " "card_name=<name for the card> " + "card_properties=<properties for the card> " "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "address=<address of the device> " "profile=<a2dp|hsp> " "rate=<sample rate> " @@ -84,8 +87,11 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "name", "card_name", + "card_properties", "sink_name", + "sink_properties", "source_name", + "source_properties", "address", "profile", "rate", @@ -174,6 +180,8 @@ struct userdata { #define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC) #define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC) +#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC) + #ifdef NOKIA #define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) #endif @@ -1296,15 +1304,37 @@ static void thread_func(void *userdata) { if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) { pa_usec_t time_passed; - uint64_t should_have_written; + pa_usec_t audio_sent; /* Hmm, there is no input stream we could synchronize * to. So let's do things by time */ time_passed = pa_rtclock_usec() - u->started_at; - should_have_written = pa_usec_to_bytes(time_passed, &u->sample_spec); + audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec); + + if (audio_sent <= time_passed) { + pa_usec_t audio_to_send = time_passed - audio_sent; + + /* Never try to catch up for more than 100ms */ + if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) { + pa_usec_t skip_usec; + uint64_t skip_bytes; + pa_memchunk tmp; + + skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC; + skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec); + + pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream", + (unsigned long long) skip_usec, + (unsigned long long) skip_bytes); - do_write = u->write_index <= should_have_written; + pa_sink_render_full(u->sink, skip_bytes, &tmp); + pa_memblock_unref(tmp.memblock); + u->write_index += skip_bytes; + } + + do_write = 1; + } } if (writable && do_write > 0) { @@ -1596,6 +1626,12 @@ static int add_sink(struct userdata *u) { data.name = get_name("sink", u->modargs, u->address, &b); data.namereg_fail = b; + if (pa_modargs_get_proplist(u->modargs, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + return -1; + } + u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY | (u->profile == PROFILE_HSP ? PA_SINK_HW_VOLUME_CTRL : 0)); pa_sink_new_data_done(&data); @@ -1608,9 +1644,9 @@ static int add_sink(struct userdata *u) { u->sink->parent.process_msg = sink_process_msg; pa_sink_set_max_request(u->sink, u->block_size); - u->sink->fixed_latency = - (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + - pa_bytes_to_usec(u->block_size, &u->sample_spec); + pa_sink_set_fixed_latency(u->sink, + (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + + pa_bytes_to_usec(u->block_size, &u->sample_spec)); } if (u->profile == PROFILE_HSP) { @@ -1648,6 +1684,12 @@ static int add_source(struct userdata *u) { data.name = get_name("source", u->modargs, u->address, &b); data.namereg_fail = b; + if (pa_modargs_get_proplist(u->modargs, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + return -1; + } + u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY | (u->profile == PROFILE_HSP ? PA_SOURCE_HW_VOLUME_CTRL : 0)); pa_source_new_data_done(&data); @@ -1659,9 +1701,9 @@ static int add_source(struct userdata *u) { u->source->userdata = u; u->source->parent.process_msg = source_process_msg; - u->source->fixed_latency = - (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + - pa_bytes_to_usec(u->block_size, &u->sample_spec); + pa_source_set_fixed_latency(u->source, + (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + + pa_bytes_to_usec(u->block_size, &u->sample_spec)); } if (u->profile == PROFILE_HSP) { @@ -1939,13 +1981,17 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { } /* Run from main thread */ -static int add_card(struct userdata *u, const char *default_profile, const pa_bluetooth_device *device) { +static int add_card(struct userdata *u, const pa_bluetooth_device *device) { pa_card_new_data data; pa_bool_t b; pa_card_profile *p; enum profile *d; const char *ff; char *n; + const char *default_profile; + + pa_assert(u); + pa_assert(device); pa_card_new_data_init(&data); data.driver = __FILE__; @@ -1966,6 +2012,12 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl data.name = get_name("card", u->modargs, device->address, &b); data.namereg_fail = b; + if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_card_new_data_done(&data); + return -1; + } + data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); /* we base hsp/a2dp availability on UUIDs. @@ -2008,7 +2060,7 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl *d = PROFILE_OFF; pa_hashmap_put(data.profiles, p->name, p); - if (default_profile) { + if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) { if (pa_hashmap_get(data.profiles, default_profile)) pa_card_new_data_set_profile(&data, default_profile); else @@ -2154,7 +2206,7 @@ int pa__init(pa_module* m) { goto fail; /* Add the card structure. This will also initialize the default profile */ - if (add_card(u, pa_modargs_get_value(ma, "profile", NULL), device) < 0) + if (add_card(u, device) < 0) goto fail; /* Connect to the BT service and query capabilities */ diff --git a/src/modules/module-jack-sink.c b/src/modules/jack/module-jack-sink.c index 31b8a96e..290038e7 100644 --- a/src/modules/module-jack-sink.c +++ b/src/modules/jack/module-jack-sink.c @@ -67,12 +67,13 @@ PA_MODULE_DESCRIPTION("JACK Sink"); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_USAGE( - "sink_name=<name of sink> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the card> " "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> " + "connect=<connect ports?>"); #define DEFAULT_SINK_NAME "jack_out" @@ -102,11 +103,12 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "server_name", "client_name", "channels", - "connect", "channel_map", + "connect", NULL }; @@ -386,6 +388,12 @@ int pa__init(pa_module*m) { 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)); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); diff --git a/src/modules/module-jack-source.c b/src/modules/jack/module-jack-source.c index 9a215c54..ef89a98e 100644 --- a/src/modules/module-jack-source.c +++ b/src/modules/jack/module-jack-source.c @@ -57,12 +57,13 @@ PA_MODULE_DESCRIPTION("JACK Source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE( - "source_name=<name of source> " + "source_name=<name for the source> " + "source_properties=<properties for the 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> " + "connect=<connect ports?>"); #define DEFAULT_SOURCE_NAME "jack_in" @@ -89,11 +90,12 @@ struct userdata { static const char* const valid_modargs[] = { "source_name", + "source_properties", "server_name", "client_name", "channels", - "connect", "channel_map", + "connect", NULL }; @@ -338,6 +340,12 @@ int pa__init(pa_module*m) { 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)); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); pa_source_new_data_done(&data); diff --git a/src/modules/module-augment-properties.c b/src/modules/module-augment-properties.c index c3e5997a..15aa3a1e 100644 --- a/src/modules/module-augment-properties.c +++ b/src/modules/module-augment-properties.c @@ -58,6 +58,7 @@ struct rule { char *process_name; char *application_name; char *icon_name; + char *role; pa_proplist *proplist; }; @@ -72,12 +73,21 @@ static void rule_free(struct rule *r) { pa_xfree(r->process_name); pa_xfree(r->application_name); pa_xfree(r->icon_name); + pa_xfree(r->role); if (r->proplist) pa_proplist_free(r->proplist); pa_xfree(r); } -static int parse_properties(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_properties( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + struct rule *r = userdata; pa_proplist *n; @@ -93,11 +103,56 @@ static int parse_properties(const char *filename, unsigned line, const char *sec return 0; } -static int check_type(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_categories( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + struct rule *r = userdata; + const char *state = NULL; + char *c; + + while ((c = pa_split(rvalue, ";", &state))) { + + if (pa_streq(c, "Game")) { + pa_xfree(r->role); + r->role = pa_xstrdup("game"); + } else if (pa_streq(c, "Telephony")) { + pa_xfree(r->role); + r->role = pa_xstrdup("phone"); + } + + pa_xfree(c); + } + + return 0; +} + +static int check_type( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + return pa_streq(rvalue, "Application") ? 0 : -1; } -static int catch_all(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int catch_all( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + return 0; } @@ -109,6 +164,7 @@ static void update_rule(struct rule *r) { { "Icon", pa_config_parse_string, NULL, "Desktop Entry" }, { "Type", check_type, NULL, "Desktop Entry" }, { "X-PulseAudio-Properties", parse_properties, NULL, "Desktop Entry" }, + { "Categories", parse_categories, NULL, "Desktop Entry" }, { NULL, catch_all, NULL, NULL }, { NULL, NULL, NULL, NULL }, }; @@ -131,7 +187,8 @@ static void update_rule(struct rule *r) { r->mtime = st.st_mtime; pa_xfree(r->application_name); pa_xfree(r->icon_name); - r->application_name = r->icon_name = NULL; + pa_xfree(r->role); + r->application_name = r->icon_name = r->role = NULL; if (r->proplist) pa_proplist_clear(r->proplist); @@ -151,6 +208,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) { if (!r->good) return; + if (r->proplist) + pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); + if (r->icon_name) if (!pa_proplist_contains(p, PA_PROP_APPLICATION_ICON_NAME)) pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, r->icon_name); @@ -164,8 +224,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) { pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, r->application_name); } - if (r->proplist) - pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); + if (r->role) + if (!pa_proplist_contains(p, PA_PROP_MEDIA_ROLE)) + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, r->role); } static void make_room(pa_hashmap *cache) { diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c index 17f1f8c3..85478d12 100644 --- a/src/modules/module-card-restore.c +++ b/src/modules/module-card-restore.c @@ -30,7 +30,6 @@ #include <stdio.h> #include <stdlib.h> #include <ctype.h> -#include <gdbm.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> @@ -45,6 +44,7 @@ #include <pulsecore/core-subscribe.h> #include <pulsecore/card.h> #include <pulsecore/namereg.h> +#include <pulsecore/database.h> #include "module-card-restore-symdef.h" @@ -65,7 +65,7 @@ struct userdata { pa_subscription *subscription; pa_hook_slot *card_new_hook_slot; pa_time_event *save_time_event; - GDBM_FILE gdbm_file; + pa_database *database; }; #define ENTRY_VERSION 1 @@ -87,31 +87,31 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct u->core->mainloop->time_free(u->save_time_event); u->save_time_event = NULL; - gdbm_sync(u->gdbm_file); + pa_database_sync(u->database); pa_log_info("Synced."); } static struct entry* read_entry(struct userdata *u, const char *name) { - datum key, data; + pa_datum key, data; struct entry *e; pa_assert(u); pa_assert(name); - key.dptr = (char*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data = gdbm_fetch(u->gdbm_file, key); + pa_zero(data); - if (!data.dptr) + if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.dsize != sizeof(struct entry)) { - pa_log_debug("Database contains entry for card %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + if (data.size != sizeof(struct entry)) { + pa_log_debug("Database contains entry for card %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); goto fail; } - e = (struct entry*) data.dptr; + e = (struct entry*) data.data; if (e->version != ENTRY_VERSION) { pa_log_debug("Version of database entry for card %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -127,7 +127,7 @@ static struct entry* read_entry(struct userdata *u, const char *name) { fail: - pa_xfree(data.dptr); + pa_datum_free(&data); return NULL; } @@ -145,7 +145,7 @@ static void trigger_save(struct userdata *u) { 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, *old; - datum key, data; + pa_datum key, data; pa_card *card; pa_assert(c); @@ -155,7 +155,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if (!(card = pa_idxset_get_by_index(c->cards, idx))) @@ -176,15 +176,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_xfree(old); } - key.dptr = card->name; - key.dsize = (int) strlen(card->name); + key.data = card->name; + key.size = strlen(card->name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); pa_log_info("Storing profile for card %s.", card->name); - gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + pa_database_set(u->database, &key, &data, TRUE); trigger_save(u); } @@ -211,10 +211,9 @@ static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname, *fn; + char *fname; pa_card *card; uint32_t idx; - int gdbm_cache_size; pa_assert(m); @@ -227,33 +226,21 @@ int pa__init(pa_module*m) { u->core = m->core; u->module = m; u->save_time_event = NULL; - u->gdbm_file = NULL; + u->database = NULL; u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CARD, subscribe_callback, u); u->card_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) card_new_hook_callback, u); - /* We include the host identifier in the file name because gdbm - * files are CPU dependant, and we don't want things to go wrong - * if we are on a multiarch system. */ - - fn = pa_sprintf_malloc("card-database."CANONICAL_HOST".gdbm"); - fname = pa_state_path(fn, TRUE); - pa_xfree(fn); - - if (!fname) + if (!(fname = pa_state_path("card-database", TRUE))) goto fail; - if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { - pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); pa_xfree(fname); goto fail; } - /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ - gdbm_cache_size = 10; - gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); - pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); @@ -289,8 +276,8 @@ void pa__done(pa_module*m) { if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); - if (u->gdbm_file) - gdbm_close(u->gdbm_file); + if (u->database) + pa_database_close(u->database); pa_xfree(u); } diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index a1ef8da4..725faa0c 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -55,12 +55,13 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "slaves=<slave sinks> " "adjust_time=<seconds> " "resample_method=<method> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "combined" @@ -73,12 +74,13 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "slaves", "adjust_time", "resample_method", "format", - "channels", "rate", + "channels", "channel_map", NULL }; @@ -591,7 +593,7 @@ static void unsuspend(struct userdata *u) { /* Let's resume */ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { - pa_sink_suspend(o->sink, FALSE); + pa_sink_suspend(o->sink, FALSE, PA_SUSPEND_IDLE); if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) enable_output(o); @@ -871,7 +873,7 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) { } if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) { - pa_sink_suspend(sink, FALSE); + pa_sink_suspend(sink, FALSE, PA_SUSPEND_IDLE); if (PA_SINK_IS_OPENED(pa_sink_get_state(sink))) if (output_create_sink_input(o) < 0) @@ -1080,6 +1082,12 @@ int pa__init(pa_module*m) { if (slaves) pa_proplist_sets(data.proplist, "combine.slaves", slaves); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4 index f9924cfa..b6a60b6a 100644 --- a/src/modules/module-defs.h.m4 +++ b/src/modules/module-defs.h.m4 @@ -17,6 +17,7 @@ gen_symbol(pa__get_author) gen_symbol(pa__get_description) gen_symbol(pa__get_usage) gen_symbol(pa__get_version) +gen_symbol(pa__get_deprecated) gen_symbol(pa__load_once) gen_symbol(pa__get_n_used) @@ -28,6 +29,7 @@ const char* pa__get_author(void); const char* pa__get_description(void); const char* pa__get_usage(void); const char* pa__get_version(void); +const char* pa__get_deprecated(void); pa_bool_t pa__load_once(void); #endif diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 49127abc..18479df3 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -50,6 +50,7 @@ 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>"); +PA_MODULE_DEPRECATED("Please use module-hal-detect instead of module-detect!"); static const char* const valid_modargs[] = { "just-one", diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index 7d87ca0f..ae21acd5 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -30,7 +30,6 @@ #include <stdio.h> #include <stdlib.h> #include <ctype.h> -#include <gdbm.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> @@ -46,6 +45,7 @@ #include <pulsecore/sink-input.h> #include <pulsecore/source-output.h> #include <pulsecore/namereg.h> +#include <pulsecore/database.h> #include "module-device-restore-symdef.h" @@ -73,7 +73,7 @@ struct userdata { *sink_fixate_hook_slot, *source_fixate_hook_slot; pa_time_event *save_time_event; - GDBM_FILE gdbm_file; + pa_database *database; pa_bool_t restore_volume:1; pa_bool_t restore_muted:1; @@ -100,31 +100,31 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct u->core->mainloop->time_free(u->save_time_event); u->save_time_event = NULL; - gdbm_sync(u->gdbm_file); + pa_database_sync(u->database); pa_log_info("Synced."); } static struct entry* read_entry(struct userdata *u, const char *name) { - datum key, data; + pa_datum key, data; struct entry *e; pa_assert(u); pa_assert(name); - key.dptr = (char*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data = gdbm_fetch(u->gdbm_file, key); + pa_zero(data); - if (!data.dptr) + if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.dsize != sizeof(struct entry)) { - pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + if (data.size != sizeof(struct entry)) { + pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); goto fail; } - e = (struct entry*) data.dptr; + e = (struct entry*) data.data; if (e->version != ENTRY_VERSION) { pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -150,7 +150,7 @@ static struct entry* read_entry(struct userdata *u, const char *name) { fail: - pa_xfree(data.dptr); + pa_datum_free(&data); return NULL; } @@ -169,7 +169,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 struct userdata *u = userdata; struct entry entry, *old; char *name; - datum key, data; + pa_datum key, data; pa_assert(c); pa_assert(u); @@ -180,7 +180,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { @@ -221,15 +221,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_xfree(old); } - key.dptr = name; - key.dsize = (int) strlen(name); + key.data = name; + key.size = strlen(name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); pa_log_info("Storing volume/mute for device %s.", name); - gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + pa_database_set(u->database, &key, &data, TRUE); pa_xfree(name); @@ -312,12 +312,11 @@ static pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_da int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname, *fn; + char *fname; pa_sink *sink; pa_source *source; uint32_t idx; pa_bool_t restore_volume = TRUE, restore_muted = TRUE; - int gdbm_cache_size; pa_assert(m); @@ -341,7 +340,7 @@ int pa__init(pa_module*m) { u->save_time_event = NULL; u->restore_volume = restore_volume; u->restore_muted = restore_muted; - u->gdbm_file = NULL; + u->database = NULL; u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u); @@ -350,27 +349,15 @@ int pa__init(pa_module*m) { 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); } - /* We include the host identifier in the file name because gdbm - * files are CPU dependant, and we don't want things to go wrong - * if we are on a multiarch system. */ - - fn = pa_sprintf_malloc("device-volumes."CANONICAL_HOST".gdbm"); - fname = pa_state_path(fn, TRUE); - pa_xfree(fn); - - if (!fname) + if (!(fname = pa_state_path("device-volumes", TRUE))) goto fail; - if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { - pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); pa_xfree(fname); goto fail; } - /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ - gdbm_cache_size = 10; - gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); - pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); @@ -411,8 +398,8 @@ void pa__done(pa_module*m) { if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); - if (u->gdbm_file) - gdbm_close(u->gdbm_file); + if (u->database) + pa_database_close(u->database); pa_xfree(u); } diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index a1a783aa..8cb25c51 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -68,10 +68,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "server=<address> cookie=<filename> " "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate>"); + "rate=<sample rate> " + "channels=<number of channels>"); #define DEFAULT_SINK_NAME "esound_out" @@ -118,12 +119,13 @@ struct userdata { }; static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", "server", "cookie", - "rate", "format", + "rate", "channels", - "sink_name", NULL }; @@ -586,6 +588,12 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "esd"); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "EsounD Output on %s", espeaker); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); pa_sink_new_data_done(&data); diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index b6139e43..658b3e55 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -64,6 +64,7 @@ PA_MODULE_USAGE("api=<alsa> " #elif defined(HAVE_OSS) PA_MODULE_USAGE("api=<oss>"); #endif +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!"); struct device { char *udi, *originating_udi; @@ -232,7 +233,7 @@ static int hal_device_load_alsa(struct userdata *u, const char *udi, struct devi goto fail; card_name = pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi)); - args = pa_sprintf_malloc("device_id=%u name=%s card_name=%s tsched=%i", card, strip_udi(originating_udi), card_name, (int) u->use_tsched); + args = pa_sprintf_malloc("device_id=%u name=\"%s\" card_name=\"%s\" tsched=%i card_properties=\"module-hal-detect.discovered=1\"", card, strip_udi(originating_udi), card_name, (int) u->use_tsched); pa_log_debug("Loading module-alsa-card with arguments '%s'", args); m = pa_module_load(u->core, "module-alsa-card", args); @@ -567,7 +568,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_sink *sink; if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) { - pa_bool_t success = pa_sink_suspend(sink, suspend) >= 0; + pa_bool_t success = pa_sink_suspend(sink, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -580,7 +581,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_source *source; if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) { - pa_bool_t success = pa_source_suspend(source, suspend) >= 0; + pa_bool_t success = pa_source_suspend(source, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -593,7 +594,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_card *card; if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) { - pa_bool_t success = pa_card_suspend(card, suspend) >= 0; + pa_bool_t success = pa_card_suspend(card, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -637,21 +638,21 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_sink *sink; if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) - pa_sink_suspend(sink, FALSE); + pa_sink_suspend(sink, FALSE, PA_SUSPEND_SESSION); } if (d->source_name) { pa_source *source; if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) - pa_source_suspend(source, FALSE); + pa_source_suspend(source, FALSE, PA_SUSPEND_SESSION); } if (d->card_name) { pa_card *card; if ((card = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_CARD))) - pa_card_suspend(card, FALSE); + pa_card_suspend(card, FALSE, PA_SUSPEND_SESSION); } } diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index 44052c9c..15af74a6 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -50,10 +50,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "master=<name of sink to remap> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map> " "plugin=<ladspa plugin name> " "label=<ladspa plugin label> " @@ -85,10 +86,11 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "master", "format", - "channels", "rate", + "channels", "channel_map", "plugin", "label", @@ -705,6 +707,12 @@ int pa__init(pa_module*m) { 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); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&sink_data); diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index d9bab6bd..30a99ca7 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -54,12 +54,12 @@ PA_MODULE_DESCRIPTION("Clocked NULL sink"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( + "sink_name=<name of sink> " + "sink_properties=<properties for the sink> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " - "sink_name=<name of sink> " - "channel_map=<channel map> " - "description=<description for the sink>"); + "channels=<number of channels> " + "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "null" #define BLOCK_USEC (PA_USEC_PER_SEC * 2) @@ -78,12 +78,13 @@ struct userdata { }; static const char* const valid_modargs[] = { - "rate", + "sink_name", + "sink_properties", "format", + "rate", "channels", - "sink_name", "channel_map", - "description", + "description", /* supported for compatibility reasons, made redundant by sink_properties= */ NULL }; @@ -289,6 +290,12 @@ int pa__init(pa_module*m) { 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"); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&data); diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index 9d3e55d8..5b0f6414 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -54,10 +54,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "file=<path of the FIFO> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate>" + "channels=<number of channels> " "channel_map=<channel map>"); #define DEFAULT_FILE_NAME "fifo_output" @@ -83,11 +84,12 @@ struct userdata { }; static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", "file", - "rate", "format", + "rate", "channels", - "sink_name", "channel_map", NULL }; @@ -279,6 +281,12 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); @@ -293,7 +301,7 @@ int pa__init(pa_module*m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); pa_sink_set_max_request(u->sink, PIPE_BUF); - u->sink->fixed_latency = pa_bytes_to_usec(PIPE_BUF, &u->sink->sample_spec); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(PIPE_BUF, &u->sink->sample_spec)); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index df72d798..61c9fc0e 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -54,10 +54,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "source_name=<name for the source> " + "source_properties=<properties for the source> " "file=<path of the FIFO> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map>"); #define DEFAULT_FILE_NAME "/tmp/music.input" @@ -81,11 +82,12 @@ struct userdata { }; static const char* const valid_modargs[] = { + "source_name", + "source_properties", "file", + "format", "rate", "channels", - "format", - "source_name", "channel_map", NULL }; @@ -264,6 +266,12 @@ int pa__init(pa_module*m) { pa_source_new_data_set_sample_spec(&data, &ss); pa_source_new_data_set_channel_map(&data, &map); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); pa_source_new_data_done(&data); @@ -277,7 +285,7 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - u->source->fixed_latency = pa_bytes_to_usec(PIPE_BUF, &u->source->sample_spec); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(PIPE_BUF, &u->source->sample_spec)); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index ce3dcd03..5b351d19 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -251,7 +251,7 @@ int pa__init(pa_module*m) { int r; #endif -#if defined(USE_PROTOCOL_NATIVE) +#if defined(USE_PROTOCOL_NATIVE) || defined(USE_PROTOCOL_HTTP) char t[256]; #endif @@ -382,6 +382,24 @@ int pa__init(pa_module*m) { # endif #endif +#if defined(USE_PROTOCOL_HTTP) +#if defined(USE_TCP_SOCKETS) + if (u->socket_server_ipv4) + if (pa_socket_server_get_address(u->socket_server_ipv4, t, sizeof(t))) + pa_http_protocol_add_server_string(u->http_protocol, t); + +#ifdef HAVE_IPV6 + if (u->socket_server_ipv6) + if (pa_socket_server_get_address(u->socket_server_ipv6, t, sizeof(t))) + pa_http_protocol_add_server_string(u->http_protocol, t); +#endif /* HAVE_IPV6 */ +#else /* USE_TCP_SOCKETS */ + if (pa_socket_server_get_address(u->socket_server_unix, t, sizeof(t))) + pa_http_protocol_add_server_string(u->http_protocol, t); + +#endif /* USE_TCP_SOCKETS */ +#endif /* USE_PROTOCOL_HTTP */ + if (ma) pa_modargs_free(ma); @@ -419,6 +437,24 @@ void pa__done(pa_module*m) { } #elif defined(USE_PROTOCOL_HTTP) if (u->http_protocol) { + char t[256]; + +#if defined(USE_TCP_SOCKETS) + if (u->socket_server_ipv4) + if (pa_socket_server_get_address(u->socket_server_ipv4, t, sizeof(t))) + pa_http_protocol_remove_server_string(u->http_protocol, t); + +#ifdef HAVE_IPV6 + if (u->socket_server_ipv6) + if (pa_socket_server_get_address(u->socket_server_ipv6, t, sizeof(t))) + pa_http_protocol_remove_server_string(u->http_protocol, t); +#endif /* HAVE_IPV6 */ +#else /* USE_TCP_SOCKETS */ + if (u->socket_server_unix) + if (pa_socket_server_get_address(u->socket_server_unix, t, sizeof(t))) + pa_http_protocol_remove_server_string(u->http_protocol, t); +#endif /* USE_PROTOCOL_HTTP */ + pa_http_protocol_disconnect(u->http_protocol, u->module); pa_http_protocol_unref(u->http_protocol); } diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 4d68b1b0..052a3a5e 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -72,11 +72,11 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " - "description=<description for the sink> " + "sink_properties=<properties for the sink> " "server=<address> " "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate>"); + "rate=<sample rate> " + "channels=<number of channels>"); #define DEFAULT_SINK_NAME "raop" @@ -118,12 +118,13 @@ struct userdata { }; static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", "server", - "rate", "format", + "rate", "channels", - "sink_name", - "description", + "description", /* supported for compatibility reasons, made redundant by sink_properties= */ NULL }; @@ -587,6 +588,12 @@ int pa__init(pa_module*m) { else pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); pa_sink_new_data_done(&data); diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index f9777bef..119f5b9f 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -44,6 +44,7 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "master=<name of sink to remap> " "master_channel_map=<channel map> " "format=<sample format> " @@ -62,10 +63,11 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "master", "master_channel_map", - "rate", "format", + "rate", "channels", "channel_map", "remix", @@ -354,6 +356,12 @@ int pa__init(pa_module*m) { pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&sink_data); diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 7c99a9b9..c22711ae 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -31,6 +31,7 @@ #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> #include "module-rescue-streams-symdef.h" @@ -49,6 +50,7 @@ struct userdata { static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { pa_sink_input *i; + uint32_t idx; pa_sink *target; pa_assert(c); @@ -58,15 +60,14 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user if (c->state == PA_CORE_SHUTDOWN) return PA_HOOK_OK; - if (!pa_idxset_size(sink->inputs)) { + if (pa_idxset_size(sink->inputs) <= 0) { pa_log_debug("No sink inputs to move away."); return PA_HOOK_OK; } if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK)) || target == sink) { - uint32_t idx; - for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) + PA_IDXSET_FOREACH(target, c->sinks, idx) if (target != sink) break; @@ -76,20 +77,24 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user } } - while ((i = pa_idxset_first(sink->inputs, NULL))) { + pa_assert(target != sink); + + PA_IDXSET_FOREACH(i, sink->inputs, idx) { if (pa_sink_input_move_to(i, target, FALSE) < 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); + pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); else - pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); + pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, + pa_strnull(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; + uint32_t idx; pa_assert(c); pa_assert(source); @@ -98,15 +103,14 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void if (c->state == PA_CORE_SHUTDOWN) return PA_HOOK_OK; - if (!pa_idxset_size(source->outputs)) { + if (pa_idxset_size(source->outputs) <= 0) { pa_log_debug("No source outputs to move away."); return PA_HOOK_OK; } if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE)) || target == source) { - uint32_t idx; - for (target = pa_idxset_first(c->sources, &idx); target; target = pa_idxset_next(c->sources, &idx)) + PA_IDXSET_FOREACH(target, c->sources, idx) if (target != source && !target->monitor_of == !source->monitor_of) break; @@ -118,19 +122,20 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void pa_assert(target != source); - while ((o = pa_idxset_first(source->outputs, NULL))) { + PA_IDXSET_FOREACH(o, source->outputs, idx) { if (pa_source_output_move_to(o, target, FALSE) < 0) - 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); + pa_log_info("Failed to move source output %u \"%s\" to %s.", o->index, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name); else - pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); + pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name); } - return PA_HOOK_OK; } int pa__init(pa_module*m) { - pa_modargs *ma = NULL; + pa_modargs *ma; struct userdata *u; pa_assert(m); @@ -153,10 +158,9 @@ void pa__done(pa_module*m) { pa_assert(m); - if (!m->userdata) + if (!(u = m->userdata)) return; - u = m->userdata; if (u->sink_slot) pa_hook_slot_free(u->sink_slot); if (u->source_slot) diff --git a/src/modules/module-rygel-media-server.c b/src/modules/module-rygel-media-server.c new file mode 100644 index 00000000..4c02e958 --- /dev/null +++ b/src/modules/module-rygel-media-server.c @@ -0,0 +1,847 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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 <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/i18n.h> +#include <pulse/utf8.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/modargs.h> +#include <pulsecore/dbus-shared.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/namereg.h> +#include <pulsecore/mime-type.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/protocol-http.h> +#include <pulsecore/parseaddr.h> + +#include "module-rygel-media-server-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "display_name=<UPnP Media Server name>"); + +/* This implements http://live.gnome.org/Rygel/MediaServerSpec */ + +#define SERVICE_NAME "org.gnome.UPnP.MediaServer1.PulseAudio" + +#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio" +#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks" +#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer1/PulseAudio/Sources" + +#define CONTAINER_INTROSPECT_XML_PREFIX \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <!-- If you are looking for documentation make sure to check out" \ + " http://live.gnome.org/Rygel/MediaServerSpec -->" \ + " <interface name=\"org.gnome.UPnP.MediaContainer1\">" \ + " <signal name=\"Updated\">" \ + " <arg name=\"path\" type=\"o\"/>" \ + " </signal>" \ + " <property name=\"Items\" type=\"ao\" access=\"read\"/>" \ + " <property name=\"ItemCount\" type=\"u\" access=\"read\"/>" \ + " <property name=\"Containers\" type=\"ao\" access=\"read\"/>" \ + " <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ + " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ + " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Properties\">" \ + " <method name=\"Get\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \ + " </method>" \ + " <method name=\"GetAll\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \ + " </method>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ + " <method name=\"Introspect\">" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ + " </method>" \ + " </interface>" + +#define CONTAINER_INTROSPECT_XML_POSTFIX \ + "</node>" + +#define ROOT_INTROSPECT_XML \ + CONTAINER_INTROSPECT_XML_PREFIX \ + "<node name=\"Sinks\"/>" \ + "<node name=\"Sources\"/>" \ + CONTAINER_INTROSPECT_XML_POSTFIX + +#define ITEM_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <!-- If you are looking for documentation make sure to check out" \ + " http://live.gnome.org/Rygel/MediaProviderSpec -->" \ + " <interface name=\"org.gnome.UPnP.MediaItem1\">" \ + " <property name=\"URLs\" type=\"as\" access=\"read\"/>" \ + " <property name=\"MIMEType\" type=\"s\" access=\"read\"/>" \ + " <property name=\"Type\" type=\"s\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ + " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ + " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Properties\">" \ + " <method name=\"Get\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \ + " </method>" \ + " <method name=\"GetAll\">" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \ + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \ + " </method>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ + " <method name=\"Introspect\">" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ + " </method>" \ + " </interface>" \ + "</node>" + + +static const char* const valid_modargs[] = { + "display_name", + NULL +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_dbus_connection *bus; + pa_bool_t got_name:1; + + char *display_name; + + pa_hook_slot *source_new_slot, *source_unlink_slot; + + pa_http_protocol *http; +}; + +static void send_signal(struct userdata *u, pa_source *s) { + DBusMessage *m; + const char *parent; + + pa_assert(u); + pa_source_assert_ref(s); + + if (u->core->state == PA_CORE_SHUTDOWN) + return; + + if (s->monitor_of) + parent = OBJECT_SINKS; + else + parent = OBJECT_SOURCES; + + pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer1", "Updated")); + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL)); + + dbus_message_unref(m); +} + +static pa_hook_result_t source_new_or_unlink_cb(pa_core *c, pa_source *s, struct userdata *u) { + pa_assert(c); + pa_source_assert_ref(s); + + send_signal(u, s); + + return PA_HOOK_OK; +} + +static pa_bool_t message_is_property_get(DBusMessage *m, const char *interface, const char *property) { + const char *i, *p; + DBusError error; + + dbus_error_init(&error); + + pa_assert(m); + + if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get")) + return FALSE; + + if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_STRING, &p, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + dbus_error_free(&error); + return FALSE; + } + + return pa_streq(i, interface) && pa_streq(p, property); +} + +static pa_bool_t message_is_property_get_all(DBusMessage *m, const char *interface) { + const char *i; + DBusError error; + + dbus_error_init(&error); + + pa_assert(m); + + if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll")) + return FALSE; + + if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + dbus_error_free(&error); + return FALSE; + } + + return pa_streq(i, interface); +} + +static void append_variant_object_array(DBusMessage *m, DBusMessageIter *iter, const char *path[], unsigned n) { + DBusMessageIter _iter, variant, array; + unsigned c; + + pa_assert(m); + pa_assert(path); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "ao", &variant)); + pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "o", &array)); + + for (c = 0; c < n; c++) + pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, path + c)); + + pa_assert_se(dbus_message_iter_close_container(&variant, &array)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant)); +} + +static void append_variant_string(DBusMessage *m, DBusMessageIter *iter, const char *s) { + DBusMessageIter _iter, sub; + + pa_assert(m); + pa_assert(s); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &s)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_object(DBusMessage *m, DBusMessageIter *iter, const char *s) { + DBusMessageIter _iter, sub; + + pa_assert(m); + pa_assert(s); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "o", &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &s)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsigned u) { + DBusMessageIter _iter, sub; + + pa_assert(m); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "u", &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_object_array(m, &sub, path, n); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_string(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_string(m, &sub, value); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_object(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_object(m, &sub, value); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter *iter, const char *name, unsigned u) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_unsigned(m, &sub, u); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES }; +static const char *array_no_children[] = { }; + +static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + DBusMessage *r = NULL; + + pa_assert(u); + + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, (const char**) array_root_containers, PA_ELEMENTSOF(array_root_containers)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object_array(r, &sub, "Containers", array_root_containers, PA_ELEMENTSOF(array_root_containers)); + append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers)); + append_property_dict_entry_object_array(r, &sub, "Items", array_no_children, PA_ELEMENTSOF(array_no_children)); + append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children)); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, OBJECT_ROOT); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, u->display_name); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = ROOT_INTROSPECT_XML; + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + } else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (r) { + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static char *compute_url(struct userdata *u, const char *name) { + pa_strlist *i; + + pa_assert(u); + pa_assert(name); + + for (i = pa_http_protocol_servers(u->http); i; i = pa_strlist_next(i)) { + pa_parsed_address a; + + if (pa_parse_address(pa_strlist_data(i), &a) >= 0 && + (a.type == PA_PARSED_ADDRESS_TCP4 || + a.type == PA_PARSED_ADDRESS_TCP6 || + a.type == PA_PARSED_ADDRESS_TCP_AUTO)) { + + const char *address; + char *s; + + if (pa_is_ip_address(a.path_or_host)) + address = a.path_or_host; + else + address = "@ADDRESS@"; + + if (a.port <= 0) + a.port = 4714; + + s = pa_sprintf_malloc("http://%s:%u/listen/source/%s", address, a.port, name); + + pa_xfree(a.path_or_host); + return s; + } + + pa_xfree(a.path_or_host); + } + + return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name); +} + +static char **child_array(struct userdata *u, const char *path, unsigned *n) { + unsigned m; + uint32_t idx; + char **array; + + pa_assert(u); + pa_assert(path); + pa_assert(n); + + if (pa_streq(path, OBJECT_SINKS)) + m = pa_idxset_size(u->core->sinks); + else + m = pa_idxset_size(u->core->sources); + + array = pa_xnew(char*, m); + *n = 0; + + if (pa_streq(path, OBJECT_SINKS)) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) + array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index); + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) + if (!source->monitor_of) + array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index); + } + + pa_assert((*n) <= m); + + return array; +} + +static void free_child_array(char **array, unsigned n) { + + for (; n >= 1; n--) + pa_xfree(array[n-1]); + + pa_xfree(array); +} + +static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + DBusMessage *r = NULL; + const char *path; + + pa_assert(u); + + path = dbus_message_get_path(m); + + if (pa_streq(path, OBJECT_SINKS) || pa_streq(path, OBJECT_SOURCES)) { + + /* Container nodes */ + + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { + char ** array; + unsigned n; + + array = child_array(u, path, &n); + + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object_array(r, NULL, (const char**) array, n); + + free_child_array(array, n); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_unsigned(r, NULL, + pa_streq(path, OBJECT_SINKS) ? + pa_idxset_size(u->core->sinks) : + pa_idxset_size(u->core->sources)); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { + DBusMessageIter iter, sub; + char **array; + unsigned n; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object_array(r, &sub, "Containers", array_no_children, PA_ELEMENTSOF(array_no_children)); + append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0); + + array = child_array(u, path, &n); + append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n); + free_child_array(array, n); + append_property_dict_entry_unsigned(r, &sub, "ItemCount", + pa_streq(path, OBJECT_SINKS) ? + pa_idxset_size(u->core->sinks) : + pa_idxset_size(u->core->sources)); + + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, OBJECT_ROOT); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, + NULL, + pa_streq(path, OBJECT_SINKS) ? + _("Output Devices") : + _("Input Devices")); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object(m, &sub, "Parent", OBJECT_ROOT); + append_property_dict_entry_string(m, &sub, "DisplayName", + pa_streq(path, OBJECT_SINKS) ? + _("Output Devices") : + _("Input Devices")); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + pa_strbuf *sb; + char *xml; + uint32_t idx; + + sb = pa_strbuf_new(); + pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_PREFIX); + + if (pa_streq(path, OBJECT_SINKS)) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) + pa_strbuf_printf(sb, "<node name=\"%u\"/>", sink->index); + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) + if (!source->monitor_of) + pa_strbuf_printf(sb, "<node name=\"%u\"/>", source->index); + } + + pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_POSTFIX); + xml = pa_strbuf_tostring_free(sb); + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + pa_xfree(xml); + } else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + } else { + pa_sink *sink = NULL; + pa_source *source = NULL; + + /* Child nodes */ + + if (pa_startswith(path, OBJECT_SINKS "/")) + sink = pa_namereg_get(u->core, path + sizeof(OBJECT_SINKS), PA_NAMEREG_SINK); + else if (pa_startswith(path, OBJECT_SOURCES "/")) + source = pa_namereg_get(u->core, path + sizeof(OBJECT_SOURCES), PA_NAMEREG_SOURCE); + + if (!sink && !source) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); + append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "Type")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, "audio"); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "MIMEType")) { + char *t; + + if (sink) + t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + else + t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, t); + pa_xfree(t); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "URLs")) { + DBusMessageIter iter, sub, array; + char *url; + + pa_assert_se(r = dbus_message_new_method_return(m)); + + dbus_message_iter_init_append(r, &iter); + + url = compute_url(u, sink ? sink->monitor_source->name : source->name); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &sub)); + pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array)); + pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); + pa_assert_se(dbus_message_iter_close_container(&sub, &array)); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + pa_xfree(url); + + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem1")) { + DBusMessageIter iter, sub, dict, variant, array; + char *url, *t; + const char *un = "URLs"; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_string(r, &sub, "Type", "audio"); + + if (sink) + t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + else + t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + + append_property_dict_entry_string(r, &sub, "MIMEType", t); + pa_xfree(t); + + pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &dict)); + pa_assert_se(dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &un)); + + url = compute_url(u, sink ? sink->monitor_source->name : source->name); + + pa_assert_se(dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "as", &variant)); + pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array)); + pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); + pa_assert_se(dbus_message_iter_close_container(&variant, &array)); + pa_assert_se(dbus_message_iter_close_container(&dict, &variant)); + pa_assert_se(dbus_message_iter_close_container(&sub, &dict)); + + pa_xfree(url); + + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = + ITEM_INTROSPECT_XML; + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + } else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (r) { + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +int pa__init(pa_module *m) { + + struct userdata *u; + pa_modargs *ma = NULL; + DBusError error; + const char *t; + + static const DBusObjectPathVTable vtable_root = { + .message_function = root_handler, + }; + static const DBusObjectPathVTable vtable_sinks_and_sources = { + .message_function = sinks_and_sources_handler, + }; + + dbus_error_init(&error); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->http = pa_http_protocol_get(u->core); + + if ((t = pa_modargs_get_value(ma, "display_name", NULL))) + u->display_name = pa_utf8_filter(t); + else + u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@")); + + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u); + + if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) { + pa_log("Failed to get session bus connection: %s", error.message); + goto fail; + } + + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u)); + pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u)); + pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u)); + + if (dbus_bus_request_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message); + goto fail; + } + + u->got_name = TRUE; + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + dbus_error_free(&error); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata*u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + 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->bus) { + DBusError error; + + dbus_error_init(&error); + + dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS); + dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES); + + if (u->got_name) { + if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) { + pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message); + dbus_error_free(&error); + } + } + + pa_dbus_connection_unref(u->bus); + } + + pa_xfree(u->display_name); + + if (u->http) + pa_http_protocol_unref(u->http); + + pa_xfree(u); +} diff --git a/src/modules/module-sine-source.c b/src/modules/module-sine-source.c index a5f1ce70..14a04e47 100644 --- a/src/modules/module-sine-source.c +++ b/src/modules/module-sine-source.c @@ -55,8 +55,9 @@ PA_MODULE_DESCRIPTION("Sine wave generator source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( - "rate=<sample rate> " "source_name=<name for the source> " + "source_properties=<properties for the source> " + "rate=<sample rate> " "frequency=<frequency in Hz>"); #define DEFAULT_SOURCE_NAME "sine_input" @@ -79,8 +80,9 @@ struct userdata { }; static const char* const valid_modargs[] = { - "rate", "source_name", + "source_properties", + "rate", "frequency", NULL }; @@ -248,6 +250,12 @@ int pa__init(pa_module*m) { pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency); pa_source_new_data_set_sample_spec(&data, &ss); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); pa_source_new_data_done(&data); @@ -264,8 +272,7 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - u->source->fixed_latency = u->block_usec; - + pa_source_set_fixed_latency(u->source, u->block_usec); if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index 3f46406a..5cfa97a7 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -68,7 +68,9 @@ PA_MODULE_DESCRIPTION("Solaris Sink/Source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "device=<audio device file name> " "record=<enable source?> " "playback=<enable sink?> " @@ -112,7 +114,9 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "source_name", + "source_properties", "device", "record", "playback", @@ -897,10 +901,16 @@ int pa__init(pa_module *m) { pa_source_new_data_set_channel_map(&source_new_data, &map); pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name); pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_API, "solaris"); - pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM source"); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM source"); pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "serial"); pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) u->buffer_size); + if (pa_modargs_get_proplist(ma, "source_properties", source_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_new_data); + goto fail; + } + u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL); pa_source_new_data_done(&source_new_data); pa_xfree(name_buf); @@ -939,9 +949,15 @@ int pa__init(pa_module *m) { pa_sink_new_data_set_channel_map(&sink_new_data, &map); pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name); pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_API, "solaris"); - pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM sink"); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Solaris PCM sink"); pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "serial"); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_new_data); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL); pa_sink_new_data_done(&sink_new_data); diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 70cd89a7..2de98f4e 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -30,7 +30,6 @@ #include <stdio.h> #include <stdlib.h> #include <ctype.h> -#include <gdbm.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> @@ -49,6 +48,7 @@ #include <pulsecore/protocol-native.h> #include <pulsecore/pstream.h> #include <pulsecore/pstream-util.h> +#include <pulsecore/database.h> #include "module-stream-restore-symdef.h" @@ -81,7 +81,7 @@ struct userdata { *source_output_new_hook_slot, *connection_unlink_hook_slot; pa_time_event *save_time_event; - GDBM_FILE gdbm_file; + pa_database* database; pa_bool_t restore_device:1; pa_bool_t restore_volume:1; @@ -123,7 +123,7 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct u->core->mainloop->time_free(u->save_time_event); u->save_time_event = NULL; - gdbm_sync(u->gdbm_file); + pa_database_sync(u->database); pa_log_info("Synced."); } @@ -153,28 +153,28 @@ static char *get_name(pa_proplist *p, const char *prefix) { } static struct entry* read_entry(struct userdata *u, const char *name) { - datum key, data; + pa_datum key, data; struct entry *e; pa_assert(u); pa_assert(name); - key.dptr = (char*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data = gdbm_fetch(u->gdbm_file, key); + pa_zero(data); - if (!data.dptr) + if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.dsize != sizeof(struct entry)) { + if (data.size != sizeof(struct entry)) { /* This is probably just a database upgrade, hence let's not * consider this more than a debug message */ - pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); goto fail; } - e = (struct entry*) data.dptr; + e = (struct entry*) data.data; if (e->version != ENTRY_VERSION) { pa_log_debug("Version of database entry for stream %s doesn't match our version. Probably due to upgrade, ignoring.", name); @@ -205,7 +205,7 @@ static struct entry* read_entry(struct userdata *u, const char *name) { fail: - pa_xfree(data.dptr); + pa_datum_free(&data); return NULL; } @@ -261,7 +261,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 struct userdata *u = userdata; struct entry entry, *old; char *name; - datum key, data; + pa_datum key, data; pa_assert(c); pa_assert(u); @@ -272,7 +272,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { @@ -334,15 +334,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_xfree(old); } - key.dptr = name; - key.dsize = (int) strlen(name); + key.data = name; + key.size = strlen(name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); pa_log_info("Storing volume/mute/device for stream %s.", name); - gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + pa_database_set(u->database, &key, &data, TRUE); pa_xfree(name); @@ -471,25 +471,6 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou #define EXT_VERSION 1 -static void clear_db(struct userdata *u) { - datum key; - - pa_assert(u); - - key = gdbm_firstkey(u->gdbm_file); - while (key.dptr) { - datum next_key; - next_key = gdbm_nextkey(u->gdbm_file, key); - - gdbm_delete(u->gdbm_file, key); - pa_xfree(key.dptr); - - key = next_key; - } - - gdbm_reorganize(u->gdbm_file); -} - static void apply_entry(struct userdata *u, const char *name, struct entry *e) { pa_sink_input *si; pa_source_output *so; @@ -559,18 +540,20 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { #if 0 static void dump_database(struct userdata *u) { - datum key; + pa_datum key; + pa_bool_t done; - key = gdbm_firstkey(u->gdbm_file); - while (key.dptr) { - datum next_key; + done = !pa_database_first(u->database, &key, NULL); + + while (!done) { + pa_datum next_key; struct entry *e; char *name; - next_key = gdbm_nextkey(u->gdbm_file, key); + done = !pa_database_next(u->database, &key, &next_key, NULL); - name = pa_xstrndup(key.dptr, key.dsize); - pa_xfree(key.dptr); + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); if ((e = read_entry(u, name))) { char t[256]; @@ -618,21 +601,23 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio } case SUBCOMMAND_READ: { - datum key; + pa_datum key; + pa_bool_t done; if (!pa_tagstruct_eof(t)) goto fail; - key = gdbm_firstkey(u->gdbm_file); - while (key.dptr) { - datum next_key; + done = !pa_database_first(u->database, &key, NULL); + + while (!done) { + pa_datum next_key; struct entry *e; char *name; - next_key = gdbm_nextkey(u->gdbm_file, key); + done = !pa_database_next(u->database, &key, &next_key, NULL); - name = pa_xstrndup(key.dptr, (size_t) key.dsize); - pa_xfree(key.dptr); + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); if ((e = read_entry(u, name))) { pa_cvolume r; @@ -669,16 +654,15 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio goto fail; if (mode == PA_UPDATE_SET) - clear_db(u); + pa_database_clear(u->database); while (!pa_tagstruct_eof(t)) { const char *name, *device; pa_bool_t muted; struct entry entry; - datum key, data; - int k; + pa_datum key, data; - memset(&entry, 0, sizeof(entry)); + pa_zero(entry); entry.version = ENTRY_VERSION; if (pa_tagstruct_gets(t, &name) < 0 || @@ -708,13 +692,13 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio !pa_namereg_is_valid_name(entry.device)) goto fail; - key.dptr = (void*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - data.dptr = (void*) &entry; - data.dsize = sizeof(entry); + data.data = &entry; + data.size = sizeof(entry); - if ((k = gdbm_store(u->gdbm_file, key, data, mode == PA_UPDATE_REPLACE ? GDBM_REPLACE : GDBM_INSERT)) == 0) + if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) if (apply_immediately) apply_entry(u, name, &entry); } @@ -728,15 +712,15 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio while (!pa_tagstruct_eof(t)) { const char *name; - datum key; + pa_datum key; if (pa_tagstruct_gets(t, &name) < 0) goto fail; - key.dptr = (void*) name; - key.dsize = (int) strlen(name); + key.data = (char*) name; + key.size = strlen(name); - gdbm_delete(u->gdbm_file, key); + pa_database_unset(u->database, &key); } trigger_save(u); @@ -786,12 +770,11 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - char *fname, *fn; + char *fname; pa_sink_input *si; pa_source_output *so; uint32_t idx; pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE; - int gdbm_cache_size; pa_assert(m); @@ -817,7 +800,7 @@ int pa__init(pa_module*m) { u->restore_device = restore_device; u->restore_volume = restore_volume; u->restore_muted = restore_muted; - u->gdbm_file = NULL; + u->database = NULL; u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); u->protocol = pa_native_protocol_get(m->core); @@ -835,27 +818,18 @@ int pa__init(pa_module*m) { if (restore_volume || restore_muted) 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); - /* We include the host identifier in the file name because gdbm - * files are CPU dependant, and we don't want things to go wrong - * if we are on a multiarch system. */ - fn = pa_sprintf_malloc("stream-volumes."CANONICAL_HOST".gdbm"); - fname = pa_state_path(fn, TRUE); - pa_xfree(fn); + fname = pa_state_path("stream-volumes", TRUE); if (!fname) goto fail; - if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) { - pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); pa_xfree(fname); goto fail; } - /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ - gdbm_cache_size = 10; - gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); - pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); @@ -901,8 +875,8 @@ void pa__done(pa_module*m) { if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); - if (u->gdbm_file) - gdbm_close(u->gdbm_file); + if (u->database) + pa_database_close(u->database); if (u->protocol) { pa_native_protocol_remove_ext(u->protocol, m); diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 7e17f8f7..c5b78911 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -27,6 +27,7 @@ #include <pulse/timeval.h> #include <pulsecore/core.h> +#include <pulsecore/core-util.h> #include <pulsecore/sink-input.h> #include <pulsecore/source-output.h> #include <pulsecore/modargs.h> @@ -85,30 +86,39 @@ static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval d->userdata->core->mainloop->time_restart(d->time_event, NULL); - if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { + if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && !(d->sink->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); - pa_sink_suspend(d->sink, TRUE); + pa_sink_suspend(d->sink, TRUE, PA_SUSPEND_IDLE); } - if (d->source && pa_source_check_suspend(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { + if (d->source && pa_source_check_suspend(d->source) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Source %s idle for too long, suspending ...", d->source->name); - pa_source_suspend(d->source, TRUE); + pa_source_suspend(d->source, TRUE, PA_SUSPEND_IDLE); } } static void restart(struct device_info *d) { struct timeval tv; + const char *s; + uint32_t timeout; pa_assert(d); + pa_assert(d->sink || d->source); pa_gettimeofday(&tv); d->last_use = tv; - pa_timeval_add(&tv, d->userdata->timeout*1000000); + + s = pa_proplist_gets(d->sink ? d->sink->proplist : d->source->proplist, "module-suspend-on-idle.timeout"); + if (!s || pa_atou(s, &timeout) < 0) + timeout = d->userdata->timeout; + + pa_timeval_add(&tv, timeout * PA_USEC_PER_SEC); + d->userdata->core->mainloop->time_restart(d->time_event, &tv); if (d->sink) - pa_log_debug("Sink %s becomes idle.", d->sink->name); + pa_log_debug("Sink %s becomes idle, timeout in %u seconds.", d->sink->name, timeout); if (d->source) - pa_log_debug("Source %s becomes idle.", d->source->name); + pa_log_debug("Source %s becomes idle, timeout in %u seconds.", d->source->name, timeout); } static void resume(struct device_info *d) { @@ -117,13 +127,13 @@ static void resume(struct device_info *d) { d->userdata->core->mainloop->time_restart(d->time_event, NULL); if (d->sink) { - pa_sink_suspend(d->sink, FALSE); + pa_sink_suspend(d->sink, FALSE, PA_SUSPEND_IDLE); pa_log_debug("Sink %s becomes busy.", d->sink->name); } if (d->source) { - pa_source_suspend(d->source, FALSE); + pa_source_suspend(d->source, FALSE, PA_SUSPEND_IDLE); pa_log_debug("Source %s becomes busy.", d->source->name); } diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 5ea58aa0..6f525da3 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -64,24 +64,26 @@ #ifdef TUNNEL_SINK PA_MODULE_DESCRIPTION("Tunnel module for sinks"); PA_MODULE_USAGE( + "sink_name=<name for the local sink> " + "sink_properties=<properties for the local sink> " "server=<address> " "sink=<remote sink name> " "cookie=<filename> " "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "sink_name=<name for the local sink> " "channel_map=<channel map>"); #else PA_MODULE_DESCRIPTION("Tunnel module for sources"); PA_MODULE_USAGE( + "source_name=<name for the local source> " + "source_properties=<properties for the local source> " "server=<address> " "source=<remote source name> " "cookie=<filename> " "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "source_name=<name for the local source> " "channel_map=<channel map>"); #endif @@ -97,9 +99,11 @@ static const char* const valid_modargs[] = { "rate", #ifdef TUNNEL_SINK "sink_name", + "sink_properties", "sink", #else "source_name", + "source_properties", "source", #endif "channel_map", @@ -1873,6 +1877,12 @@ int pa__init(pa_module*m) { if (u->sink_name) pa_proplist_sets(data.proplist, "tunnel.remote.sink", u->sink_name); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL); pa_sink_new_data_done(&data); @@ -1911,6 +1921,12 @@ int pa__init(pa_module*m) { if (u->source_name) pa_proplist_sets(data.proplist, "tunnel.remote.source", u->source_name); + if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&data); + goto fail; + } + u->source = pa_source_new(m->core, &data, PA_SOURCE_NETWORK|PA_SOURCE_LATENCY); pa_source_new_data_done(&data); diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c new file mode 100644 index 00000000..1ad6fa2d --- /dev/null +++ b/src/modules/module-udev-detect.c @@ -0,0 +1,457 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <sys/inotify.h> +#include <libudev.h> + +#include <pulsecore/modargs.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> + +#include "module-udev-detect-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +struct device { + char *path; + pa_bool_t accessible; + char *card_name; + uint32_t module; +}; + +struct userdata { + pa_core *core; + pa_hashmap *devices; + pa_bool_t use_tsched; + + struct udev* udev; + struct udev_monitor *monitor; + pa_io_event *udev_io; + + int inotify_fd; + pa_io_event *inotify_io; +}; + +static const char* const valid_modargs[] = { + "tsched", + NULL +}; + +static void device_free(struct device *d) { + pa_assert(d); + + pa_xfree(d->path); + pa_xfree(d->card_name); + pa_xfree(d); +} + +static const char *path_get_card_id(const char *path) { + const char *e; + + if (!path) + return NULL; + + if (!(e = strrchr(path, '/'))) + return NULL; + + if (!pa_startswith(e, "/card")) + return NULL; + + return e + 5; +} + +static void verify_access(struct userdata *u, struct device *d) { + char *cd; + pa_card *card; + + pa_assert(u); + pa_assert(d); + + if (!(card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) + return; + + cd = pa_sprintf_malloc("%s/snd/controlC%s", udev_get_dev_path(u->udev), path_get_card_id(d->path)); + d->accessible = access(cd, W_OK) >= 0; + pa_log_info("%s is accessible: %s", cd, pa_yes_no(d->accessible)); + pa_xfree(cd); + + pa_card_suspend(card, !d->accessible, PA_SUSPEND_SESSION); +} + +static void card_changed(struct userdata *u, struct udev_device *dev) { + struct device *d; + const char *path; + const char *t; + char *card_name, *args; + pa_module *m; + char *n; + + pa_assert(u); + pa_assert(dev); + + path = udev_device_get_devpath(dev); + + if ((d = pa_hashmap_get(u->devices, path))) { + verify_access(u, d); + return; + } + + if (!(t = udev_device_get_property_value(dev, "PULSE_NAME"))) + if (!(t = udev_device_get_property_value(dev, "ID_ID"))) + if (!(t = udev_device_get_property_value(dev, "ID_PATH"))) + t = path_get_card_id(path); + + n = pa_namereg_make_valid_name(t); + + card_name = pa_sprintf_malloc("alsa_card.%s", n); + args = pa_sprintf_malloc("device_id=\"%s\" " + "name=\"%s\" " + "card_name=\"%s\" " + "tsched=%i " + "card_properties=\"module-udev-detect.discovered=1\"", + path_get_card_id(path), + n, + card_name, + (int) u->use_tsched); + + pa_log_debug("Loading module-alsa-card with arguments '%s'", args); + m = pa_module_load(u->core, "module-alsa-card", args); + pa_xfree(args); + + if (m) { + pa_log_info("Card %s (%s) added.", path, n); + + d = pa_xnew(struct device, 1); + d->path = pa_xstrdup(path); + d->card_name = card_name; + d->module = m->index; + d->accessible = TRUE; + + pa_hashmap_put(u->devices, d->path, d); + } else + pa_xfree(card_name); + + pa_xfree(n); +} + +static void remove_card(struct userdata *u, struct udev_device *dev) { + struct device *d; + + pa_assert(u); + pa_assert(dev); + + if (!(d = pa_hashmap_remove(u->devices, udev_device_get_devpath(dev)))) + return; + + pa_log_info("Card %s removed.", d->path); + pa_module_unload_request_by_index(u->core, d->module, TRUE); + device_free(d); +} + +static void process_device(struct userdata *u, struct udev_device *dev) { + const char *action, *ff; + + pa_assert(u); + pa_assert(dev); + + if (udev_device_get_property_value(dev, "PULSE_IGNORE")) { + pa_log_debug("Ignoring %s, because marked so.", udev_device_get_devpath(dev)); + return; + } + + if ((ff = udev_device_get_property_value(dev, "SOUND_FORM_FACTOR")) && + pa_streq(ff, "modem")) { + pa_log_debug("Ignoring %s, because it is a modem.", udev_device_get_devpath(dev)); + return; + } + + action = udev_device_get_action(dev); + + if (action && pa_streq(action, "remove")) + remove_card(u, dev); + else if ((!action || pa_streq(action, "change")) && + udev_device_get_property_value(dev, "SOUND_INITIALIZED")) + card_changed(u, dev); + + /* For an explanation why we don't look for 'add' events here + * have a look into /lib/udev/rules.d/78-sound-card.rules! */ +} + +static void process_path(struct userdata *u, const char *path) { + struct udev_device *dev; + + if (!path_get_card_id(path)) + return; + + if (!(dev = udev_device_new_from_syspath(u->udev, path))) { + pa_log("Failed to get udev device object from udev."); + return; + } + + process_device(u, dev); + udev_device_unref(dev); +} + +static void monitor_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct userdata *u = userdata; + struct udev_device *dev; + + pa_assert(a); + + if (!(dev = udev_monitor_receive_device(u->monitor))) { + pa_log("Failed to get udev device object from monitor."); + goto fail; + } + + if (!path_get_card_id(udev_device_get_devpath(dev))) + return; + + process_device(u, dev); + udev_device_unref(dev); + return; + +fail: + a->io_free(u->udev_io); + u->udev_io = NULL; +} + +static void inotify_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct { + struct inotify_event e; + char name[NAME_MAX]; + } buf; + struct userdata *u = userdata; + static int type = 0; + pa_bool_t verify = FALSE; + + for (;;) { + ssize_t r; + + pa_zero(buf); + if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) { + + if (r < 0 && errno == EAGAIN) + break; + + pa_log("read() from inotify failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + goto fail; + } + + if ((buf.e.mask & IN_CLOSE_WRITE) && pa_startswith(buf.e.name, "pcmC")) + verify = TRUE; + } + + if (verify) { + struct device *d; + void *state; + + pa_log_debug("Verifying access."); + + PA_HASHMAP_FOREACH(d, u->devices, state) + verify_access(u, d); + } + + return; + +fail: + a->io_free(u->inotify_io); + u->inotify_io = NULL; + + if (u->inotify_fd >= 0) { + pa_close(u->inotify_fd); + u->inotify_fd = -1; + } +} + +static int setup_inotify(struct userdata *u) { + char *dev_snd; + int r; + + if ((u->inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { + pa_log("inotify_init1() failed: %s", pa_cstrerror(errno)); + return -1; + } + + dev_snd = pa_sprintf_malloc("%s/snd", udev_get_dev_path(u->udev)); + r = inotify_add_watch(u->inotify_fd, dev_snd, IN_CLOSE_WRITE); + pa_xfree(dev_snd); + + if (r < 0) { + pa_log("inotify_add_watch() failed: %s", pa_cstrerror(errno)); + return -1; + } + + pa_assert_se(u->inotify_io = u->core->mainloop->io_new(u->core->mainloop, u->inotify_fd, PA_IO_EVENT_INPUT, inotify_cb, u)); + + return 0; +} + +int pa__init(pa_module *m) { + struct userdata *u = NULL; + pa_modargs *ma; + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + int fd; + + 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_xnew0(struct userdata, 1); + u->core = m->core; + u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->use_tsched = TRUE; + u->inotify_fd = -1; + + if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) { + pa_log("Failed to parse tsched argument."); + goto fail; + } + + if (!(u->udev = udev_new())) { + pa_log("Failed to initialize udev library."); + goto fail; + } + + if (setup_inotify(u) < 0) + goto fail; + + if (!(u->monitor = udev_monitor_new_from_netlink(u->udev, "udev"))) { + pa_log("Failed to initialize monitor."); + goto fail; + } + + errno = 0; + if (udev_monitor_enable_receiving(u->monitor) < 0) { + pa_log("Failed to enable monitor: %s", pa_cstrerror(errno)); + if (errno == EPERM) + pa_log_info("Most likely your kernel is simply too old and " + "allows only priviliged processes to listen to device events. " + "Please upgrade your kernel to at least 2.6.30."); + goto fail; + } + + if ((fd = udev_monitor_get_fd(u->monitor)) < 0) { + pa_log("Failed to get udev monitor fd."); + goto fail; + } + + pa_assert_se(u->udev_io = u->core->mainloop->io_new(u->core->mainloop, fd, PA_IO_EVENT_INPUT, monitor_cb, u)); + + if (!(enumerate = udev_enumerate_new(u->udev))) { + pa_log("Failed to initialize udev enumerator."); + goto fail; + } + + if (udev_enumerate_add_match_subsystem(enumerate, "sound") < 0) { + pa_log("Failed to match to subsystem."); + goto fail; + } + + if (udev_enumerate_scan_devices(enumerate) < 0) { + pa_log("Failed to scan for devices."); + goto fail; + } + + first = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(item, first) + process_path(u, udev_list_entry_get_name(item)); + + udev_enumerate_unref(enumerate); + + pa_log_info("Loaded %u modules.", pa_hashmap_size(u->devices)); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (enumerate) + udev_enumerate_unref(enumerate); + + 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->udev_io) + m->core->mainloop->io_free(u->udev_io); + + if (u->monitor) + udev_monitor_unref(u->monitor); + + if (u->udev) + udev_unref(u->udev); + + if (u->inotify_io) + m->core->mainloop->io_free(u->inotify_io); + + if (u->inotify_fd >= 0) + pa_close(u->inotify_fd); + + if (u->devices) { + struct device *d; + + while ((d = pa_hashmap_steal_first(u->devices))) + device_free(d); + + pa_hashmap_free(u->devices, NULL, NULL); + } + + pa_xfree(u); +} diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 61858afa..91da598e 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -36,6 +36,7 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Compatibility module"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_DEPRECATED("Please use module-stream-restore instead of module-volume-restore!"); static const char* const valid_modargs[] = { "table", @@ -62,7 +63,7 @@ int pa__init(pa_module*m) { goto fail; } - pa_log_warn("module-volume-restore is obsolete. It has been replaced by module-stream-restore. We will now load the latter but please make sure to remove module-volume-restore from your configuration."); + pa_log_warn("We will now load module-stream-restore. Please make sure to remove module-volume-restore from your configuration."); t = pa_sprintf_malloc("restore_volume=%s restore_device=%s", pa_yes_no(restore_volume), pa_yes_no(restore_device)); pa_module_load(m->core, "module-stream-restore", t); diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index 692ffe91..d72d2647 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -37,6 +37,7 @@ #include <pulse/xmalloc.h> #include <pulse/util.h> +#include <pulsecore/parseaddr.h> #include <pulsecore/sink.h> #include <pulsecore/source.h> #include <pulsecore/native-common.h> @@ -47,6 +48,7 @@ #include <pulsecore/modargs.h> #include <pulsecore/avahi-wrap.h> #include <pulsecore/endianmacros.h> +#include <pulsecore/protocol-native.h> #include "module-zeroconf-publish-symdef.h" @@ -54,7 +56,6 @@ 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" @@ -67,7 +68,6 @@ PA_MODULE_USAGE("port=<IP port number>"); #define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE static const char* const valid_modargs[] = { - "port", NULL }; @@ -88,6 +88,7 @@ struct service { struct userdata { pa_core *core; pa_module *module; + AvahiPoll *avahi_poll; AvahiClient *client; @@ -96,15 +97,15 @@ struct userdata { 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; + + pa_native_protocol *native; }; -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) { +static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) { pa_assert(s); pa_assert(ret_ss); - pa_assert(ret_description); + pa_assert(ret_proplist); pa_assert(ret_subtype); if (pa_sink_isinstance(s->device)) { @@ -113,7 +114,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann *ret_ss = sink->sample_spec; *ret_map = sink->channel_map; *ret_name = sink->name; - *ret_description = pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); + *ret_proplist = sink->proplist; *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; } else if (pa_source_isinstance(s->device)) { @@ -122,7 +123,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann *ret_ss = source->sample_spec; *ret_map = source->channel_map; *ret_name = source->name; - *ret_description = pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)); + *ret_proplist = source->proplist; *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL); } else @@ -131,11 +132,24 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) { char s[128]; + char *t; 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))); + + t = pa_get_user_name_malloc(); + l = avahi_string_list_add_pair(l, "user-name", t); + pa_xfree(t); + + t = pa_machine_id(); + l = avahi_string_list_add_pair(l, "machine-id", t); + pa_xfree(t); + + t = pa_uname_string(); + l = avahi_string_list_add_pair(l, "uname", t); + pa_xfree(t); + l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s))); l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie); @@ -184,10 +198,35 @@ static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupStat static void service_free(struct service *s); +static uint16_t compute_port(struct userdata *u) { + pa_strlist *i; + + pa_assert(u); + + for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) { + pa_parsed_address a; + + if (pa_parse_address(pa_strlist_data(i), &a) >= 0 && + (a.type == PA_PARSED_ADDRESS_TCP4 || + a.type == PA_PARSED_ADDRESS_TCP6 || + a.type == PA_PARSED_ADDRESS_TCP_AUTO) && + a.port > 0) { + + pa_xfree(a.path_or_host); + return a.port; + } + + pa_xfree(a.path_or_host); + } + + return PA_NATIVE_DEFAULT_PORT; +} + static int publish_service(struct service *s) { int r = -1; AvahiStringList *txt = NULL; - const char *description = NULL, *name = NULL; + const char *name = NULL, *t; + pa_proplist *proplist = NULL; pa_sample_spec ss; pa_channel_map map; char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; @@ -214,7 +253,7 @@ static int publish_service(struct service *s) { txt = txt_record_server_data(s->userdata->core, txt); - get_service_data(s, &ss, &map, &name, &description, &subtype); + get_service_data(s, &ss, &map, &name, &proplist, &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); @@ -222,6 +261,19 @@ static int publish_service(struct service *s) { 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 ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION))) + txt = avahi_string_list_add_pair(txt, "description", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME))) + txt = avahi_string_list_add_pair(txt, "icon-name", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME))) + txt = avahi_string_list_add_pair(txt, "vendor-name", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME))) + txt = avahi_string_list_add_pair(txt, "product-name", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS))) + txt = avahi_string_list_add_pair(txt, "class", t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR))) + txt = avahi_string_list_add_pair(txt, "form-factor", t); + if (avahi_entry_group_add_service_strlst( s->entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, @@ -230,7 +282,7 @@ static int publish_service(struct service *s) { pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, NULL, NULL, - s->userdata->port, + compute_port(s->userdata), txt) < 0) { pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); @@ -287,7 +339,7 @@ finish: static struct service *get_service(struct userdata *u, pa_object *device) { struct service *s; - char hn[64], un[64]; + char *hn, *un; const char *n; pa_assert(u); @@ -309,11 +361,13 @@ static struct service *get_service(struct userdata *u, pa_object *device) { n = PA_SOURCE(device)->name; } - s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", - pa_get_user_name(un, sizeof(un)), - pa_get_host_name(hn, sizeof(hn)), - n), - AVAHI_LABEL_MAX-1); + hn = pa_get_host_name_malloc(); + un = pa_get_user_name_malloc(); + + s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), AVAHI_LABEL_MAX-1); + + pa_xfree(un); + pa_xfree(hn); pa_hashmap_put(u->services, s->device, s); @@ -430,7 +484,7 @@ static int publish_main_service(struct userdata *u) { SERVICE_TYPE_SERVER, NULL, NULL, - u->port, + compute_port(u), txt) < 0) { pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client))); @@ -552,9 +606,8 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda int pa__init(pa_module*m) { struct userdata *u; - uint32_t port = PA_NATIVE_DEFAULT_PORT; pa_modargs *ma = NULL; - char hn[256], un[256]; + char *hn, *un; int error; if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { @@ -562,15 +615,10 @@ int pa__init(pa_module*m) { goto fail; } - if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) { - pa_log("Invalid port specified."); - goto fail; - } - m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; u->module = m; - u->port = (uint16_t) port; + u->native = pa_native_protocol_get(u->core); u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); @@ -585,7 +633,11 @@ int pa__init(pa_module*m) { u->main_entry_group = NULL; - u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX); + un = pa_get_user_name_malloc(); + hn = pa_get_host_name_malloc(); + u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), AVAHI_LABEL_MAX-1); + pa_xfree(un); + pa_xfree(hn); 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)); @@ -643,6 +695,9 @@ void pa__done(pa_module*m) { if (u->avahi_poll) pa_avahi_poll_free(u->avahi_poll); + if (u->native) + pa_native_protocol_unref(u->native); + pa_xfree(u->service_name); pa_xfree(u); } diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index 9f7863f5..b1afcfd6 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -85,17 +85,22 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " "source_name=<name for the source> " + "source_properties=<properties for the source> " "device=<OSS device> " "record=<enable source?> " "playback=<enable sink?> " "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> " "mmap=<enable memory mapping?>"); +#ifdef __linux__ +PA_MODULE_DEPRECATED("Please use module-alsa-card instead of module-oss!"); +#endif #define DEFAULT_DEVICE "/dev/dsp" @@ -140,7 +145,9 @@ struct userdata { static const char* const valid_modargs[] = { "sink_name", + "sink_properties", "source_name", + "source_properties", "device", "record", "playback", @@ -477,6 +484,7 @@ static void build_pollfd(struct userdata *u) { pollfd->revents = 0; } +/* Called from IO context */ static int suspend(struct userdata *u) { pa_assert(u); pa_assert(u->fd >= 0); @@ -526,6 +534,7 @@ static int suspend(struct userdata *u) { return 0; } +/* Called from IO context */ static int unsuspend(struct userdata *u) { int m; pa_sample_spec ss, *ss_original; @@ -616,10 +625,10 @@ static int unsuspend(struct userdata *u) { build_pollfd(u); - if (u->sink) - pa_sink_get_volume(u->sink, TRUE, FALSE); - if (u->source) - pa_source_get_volume(u->source, TRUE); + if (u->sink && u->sink->get_volume) + u->sink->get_volume(u->sink); + if (u->source && u->source->get_volume) + u->source->get_volume(u->source); pa_log_info("Resumed successfully..."); @@ -631,6 +640,7 @@ fail: return -1; } +/* Called from IO 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; int ret; @@ -1311,6 +1321,12 @@ int pa__init(pa_module*m) { 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)); + if (pa_modargs_get_proplist(ma, "source_properties", source_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_new_data); + goto fail; + } + 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); @@ -1325,8 +1341,8 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->in_hwbuf_size, &u->source->sample_spec)); u->source->refresh_volume = TRUE; - u->source->fixed_latency = pa_bytes_to_usec(u->in_hwbuf_size, &u->source->sample_spec); if (use_mmap) u->in_mmap_memblocks = pa_xnew0(pa_memblock*, u->in_nfrags); @@ -1373,6 +1389,12 @@ int pa__init(pa_module*m) { 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)); + if (pa_modargs_get_proplist(ma, "sink_properties", sink_new_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_new_data); + goto fail; + } + 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); @@ -1387,8 +1409,8 @@ int pa__init(pa_module*m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->out_hwbuf_size, &u->sink->sample_spec)); u->sink->refresh_volume = TRUE; - u->sink->fixed_latency = pa_bytes_to_usec(u->out_hwbuf_size, &u->sink->sample_spec); pa_sink_set_max_request(u->sink, u->out_hwbuf_size); diff --git a/src/modules/reserve-monitor.c b/src/modules/reserve-monitor.c new file mode 100644 index 00000000..64d2a7cc --- /dev/null +++ b/src/modules/reserve-monitor.c @@ -0,0 +1,259 @@ +/*** + Copyright 2009 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include "reserve-monitor.h" + +struct rm_monitor { + int ref; + + char *device_name; + char *service_name; + + DBusConnection *connection; + + unsigned busy:1; + unsigned filtering:1; + unsigned matching:1; + + rm_change_cb_t change_cb; + void *userdata; +}; + +#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." + +static DBusHandlerResult filter_handler( + DBusConnection *c, + DBusMessage *s, + void *userdata) { + + DBusMessage *reply; + rm_monitor *m; + DBusError error; + + dbus_error_init(&error); + + m = userdata; + assert(m->ref >= 1); + + if (dbus_message_is_signal(s, "org.freedesktop.DBus", "NameOwnerChanged")) { + const char *name, *old, *new; + + if (!dbus_message_get_args( + s, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) + goto invalid; + + if (strcmp(name, m->service_name) == 0) { + + m->busy = !!(new && *new); + + if (m->change_cb) { + m->ref++; + m->change_cb(m); + rm_release(m); + } + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +invalid: + if (!(reply = dbus_message_new_error( + s, + DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"))) + goto oom; + + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +int rm_watch( + rm_monitor **_m, + DBusConnection *connection, + const char*device_name, + rm_change_cb_t change_cb, + DBusError *error) { + + rm_monitor *m = NULL; + int r; + DBusError _error; + + if (!error) + error = &_error; + + dbus_error_init(error); + + if (!_m) + return -EINVAL; + + if (!connection) + return -EINVAL; + + if (!device_name) + return -EINVAL; + + if (!(m = calloc(sizeof(rm_monitor), 1))) + return -ENOMEM; + + m->ref = 1; + + if (!(m->device_name = strdup(device_name))) { + r = -ENOMEM; + goto fail; + } + + m->connection = dbus_connection_ref(connection); + m->change_cb = change_cb; + + if (!(m->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) { + r = -ENOMEM; + goto fail; + } + sprintf(m->service_name, SERVICE_PREFIX "%s", m->device_name); + + if (!(dbus_connection_add_filter(m->connection, filter_handler, m, NULL))) { + r = -ENOMEM; + goto fail; + } + + m->filtering = 1; + + dbus_bus_add_match(m->connection, + "type='signal'," + "sender='" DBUS_SERVICE_DBUS "'," + "interface='" DBUS_INTERFACE_DBUS "'," + "member='NameOwnerChanged'", error); + + if (dbus_error_is_set(error)) { + r = -EIO; + goto fail; + } + + m->matching = 1; + + m->busy = dbus_bus_name_has_owner(m->connection, m->service_name, error); + + if (dbus_error_is_set(error)) { + r = -EIO; + goto fail; + } + + *_m = m; + return 0; + +fail: + if (&_error == error) + dbus_error_free(&_error); + + if (m) + rm_release(m); + + return r; +} + +void rm_release(rm_monitor *m) { + if (!m) + return; + + assert(m->ref > 0); + + if (--m->ref > 0) + return; + + if (m->matching) + dbus_bus_remove_match( + m->connection, + "type='signal'," + "sender='" DBUS_SERVICE_DBUS "'," + "interface='" DBUS_INTERFACE_DBUS "'," + "member='NameOwnerChanged'", NULL); + + if (m->filtering) + dbus_connection_remove_filter( + m->connection, + filter_handler, + m); + + free(m->device_name); + free(m->service_name); + + if (m->connection) + dbus_connection_unref(m->connection); + + free(m); +} + +int rm_busy(rm_monitor *m) { + if (!m) + return -EINVAL; + + assert(m->ref > 0); + + return m->busy; +} + +void rm_set_userdata(rm_monitor *m, void *userdata) { + + if (!m) + return; + + assert(m->ref > 0); + m->userdata = userdata; +} + +void* rm_get_userdata(rm_monitor *m) { + + if (!m) + return NULL; + + assert(m->ref > 0); + + return m->userdata; +} diff --git a/src/modules/reserve-monitor.h b/src/modules/reserve-monitor.h new file mode 100644 index 00000000..4f4a8332 --- /dev/null +++ b/src/modules/reserve-monitor.h @@ -0,0 +1,62 @@ +#ifndef fooreservemonitorhfoo +#define fooreservemonitorhfoo + +/*** + Copyright 2009 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <dbus/dbus.h> +#include <inttypes.h> + +typedef struct rm_monitor rm_monitor; + +/* Prototype for a function that is called whenever the reservation + * device of a device changes. Use rm_monitor_busy() to find out the + * new state.*/ +typedef void (*rm_change_cb_t)(rm_monitor *m); + +/* Creates a monitor for watching the lock status of a device. Returns + * 0 on success, a negative errno style return value on error. The + * DBus error might be set as well if the error was caused D-Bus. */ +int rm_watch( + rm_monitor **m, /* On success a pointer to the newly allocated rm_device object will be filled in here */ + DBusConnection *connection, /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */ + const char *device_name, /* The device to monitor, e.g. "Audio0" */ + rm_change_cb_t change_cb, /* Will be called whenever the lock status changes. May be NULL */ + DBusError *error); /* If we fail due to a D-Bus related issue the error will be filled in here. May be NULL. */ + +/* Free a rm_monitor object */ +void rm_release(rm_monitor *m); + +/* Checks whether the device is currently reserved, and returns 1 + * then, 0 if not, negative errno style error code value on error. */ +int rm_busy(rm_monitor *m); + +/* Attach a userdata pointer to an rm_monitor */ +void rm_set_userdata(rm_monitor *m, void *userdata); + +/* Query the userdata pointer from an rm_monitor. Returns NULL if no + * userdata was set. */ +void* rm_get_userdata(rm_monitor *m); + +#endif diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c index d0d014d3..07b592d3 100644 --- a/src/modules/reserve-wrap.c +++ b/src/modules/reserve-wrap.c @@ -35,6 +35,7 @@ #ifdef HAVE_DBUS #include <pulsecore/dbus-shared.h> #include "reserve.h" +#include "reserve-monitor.h" #endif #include "reserve-wrap.h" @@ -50,6 +51,17 @@ struct pa_reserve_wrapper { #endif }; +struct pa_reserve_monitor_wrapper { + PA_REFCNT_DECLARE; + pa_core *core; + pa_hook hook; + char *shared_name; +#ifdef HAVE_DBUS + pa_dbus_connection *connection; + struct rm_monitor *monitor; +#endif +}; + static void reserve_wrapper_free(pa_reserve_wrapper *r) { pa_assert(r); @@ -83,7 +95,7 @@ static int request_cb(rd_device *d, int forced) { PA_REFCNT_INC(r); k = pa_hook_fire(&r->hook, PA_INT_TO_PTR(forced)); - pa_log_debug("Device unlock has been requested and %s.", k < 0 ? "failed" : "succeeded"); + pa_log_debug("Device unlock of %s has been requested and %s.", r->shared_name, k < 0 ? "failed" : "succeeded"); pa_reserve_wrapper_unref(r); @@ -191,3 +203,138 @@ void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const rd_set_application_device_name(r->device, name); #endif } + +static void reserve_monitor_wrapper_free(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + +#ifdef HAVE_DBUS + if (w->monitor) + rm_release(w->monitor); + + if (w->connection) + pa_dbus_connection_unref(w->connection); +#endif + + pa_hook_done(&w->hook); + + if (w->shared_name) { + pa_assert_se(pa_shared_remove(w->core, w->shared_name) >= 0); + pa_xfree(w->shared_name); + } + + pa_xfree(w); +} + +#ifdef HAVE_DBUS +static void change_cb(rm_monitor *m) { + pa_reserve_monitor_wrapper *w; + int k; + + pa_assert(m); + pa_assert_se(w = rm_get_userdata(m)); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + PA_REFCNT_INC(w); + + if ((k = rm_busy(w->monitor)) < 0) + return; + + pa_hook_fire(&w->hook, PA_INT_TO_PTR(!!k)); + pa_log_debug("Device lock status of %s changed: %s", w->shared_name, k ? "busy" : "not busy"); + + pa_reserve_monitor_wrapper_unref(w); +} +#endif + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name) { + pa_reserve_monitor_wrapper *w; + int k; + char *t; +#ifdef HAVE_DBUS + DBusError error; + + dbus_error_init(&error); +#endif + + pa_assert(c); + pa_assert(device_name); + + t = pa_sprintf_malloc("reserve-monitor-wrapper@%s", device_name); + + if ((w = pa_shared_get(c, t))) { + pa_xfree(t); + + pa_assert(PA_REFCNT_VALUE(w) >= 1); + PA_REFCNT_INC(w); + + return w; + } + + w = pa_xnew0(pa_reserve_monitor_wrapper, 1); + PA_REFCNT_INIT(w); + w->core = c; + pa_hook_init(&w->hook, w); + w->shared_name = t; + + pa_assert_se(pa_shared_set(c, w->shared_name, w) >= 0); + +#ifdef HAVE_DBUS + if (!(w->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { + pa_log_warn("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); + + /* We don't treat this as error here because we want allow PA + * to run even when no session bus is available. */ + return w; + } + + if ((k = rm_watch( + &w->monitor, + pa_dbus_connection_get(w->connection), + device_name, + change_cb, + NULL)) < 0) { + + pa_log_warn("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k)); + goto fail; + } + + pa_log_debug("Successfully create reservation lock monitor for device '%s'", device_name); + + rm_set_userdata(w->monitor, w); + return w; + +fail: + dbus_error_free(&error); + + reserve_monitor_wrapper_free(w); + + return NULL; +#else + return w; +#endif +} + +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + if (PA_REFCNT_DEC(w) > 0) + return; + + reserve_monitor_wrapper_free(w); +} + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + return &w->hook; +} + +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + return rm_busy(w->monitor) > 0; +} diff --git a/src/modules/reserve-wrap.h b/src/modules/reserve-wrap.h index 2b97c91c..2de6c093 100644 --- a/src/modules/reserve-wrap.h +++ b/src/modules/reserve-wrap.h @@ -28,11 +28,18 @@ typedef struct pa_reserve_wrapper pa_reserve_wrapper; pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name); - void pa_reserve_wrapper_unref(pa_reserve_wrapper *r); pa_hook* pa_reserve_wrapper_hook(pa_reserve_wrapper *r); void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const char *name); +typedef struct pa_reserve_monitor_wrapper pa_reserve_monitor_wrapper; + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name); +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *m); + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *m); +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *m); + #endif diff --git a/src/modules/reserve.c b/src/modules/reserve.c index 9a9591d2..09bc46cb 100644 --- a/src/modules/reserve.c +++ b/src/modules/reserve.c @@ -43,16 +43,15 @@ struct rd_device { DBusConnection *connection; - int owning:1; - int registered:1; - int filtering:1; - int gave_up:1; + unsigned owning:1; + unsigned registered:1; + unsigned filtering:1; + unsigned gave_up:1; rd_request_cb_t request_cb; void *userdata; }; - #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/" @@ -297,6 +296,7 @@ static DBusHandlerResult filter_handler( dbus_error_init(&error); d = userdata; + assert(d->ref >= 1); if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) { const char *name; @@ -560,7 +560,7 @@ void rd_release( assert(d->ref > 0); - if (--d->ref) + if (--d->ref > 0) return; @@ -575,17 +575,11 @@ void rd_release( d->connection, d->object_path); - if (d->owning) { - DBusError error; - dbus_error_init(&error); - + if (d->owning) dbus_bus_release_name( d->connection, d->service_name, - &error); - - dbus_error_free(&error); - } + NULL); free(d->device_name); free(d->application_name); diff --git a/src/modules/reserve.h b/src/modules/reserve.h index b315a08c..31071298 100644 --- a/src/modules/reserve.h +++ b/src/modules/reserve.h @@ -45,7 +45,7 @@ typedef int (*rd_request_cb_t)( * the error was caused D-Bus. */ int rd_acquire( rd_device **d, /* On success a pointer to the newly allocated rd_device object will be filled in here */ - DBusConnection *connection, + DBusConnection *connection, /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */ const char *device_name, /* The device to lock, e.g. "Audio0" */ const char *application_name, /* A human readable name of the application, e.g. "PulseAudio Sound Server" */ int32_t priority, /* The priority for this application. If unsure use 0 */ diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index cdd2c57d..39ee4d75 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -347,8 +347,8 @@ int pa__init(pa_module*m) { o->push = source_output_push; o->kill = source_output_kill; - pa_log_info("Configured source latency of %lu ms.", - pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); + pa_log_info("Configured source latency of %llu ms.", + (unsigned long long) pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); m->userdata = o->userdata = u = pa_xnew(struct userdata, 1); u->module = m; diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 629328ad..cb037de6 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -30,6 +30,7 @@ #include <arpa/inet.h> #include <unistd.h> #include <sys/ioctl.h> +#include <netinet/in.h> #ifdef HAVE_SYS_FILIO_H #include <sys/filio.h> diff --git a/src/modules/module-x11-bell.c b/src/modules/x11/module-x11-bell.c index ac303c3b..ac303c3b 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/x11/module-x11-bell.c diff --git a/src/modules/module-x11-cork-request.c b/src/modules/x11/module-x11-cork-request.c index c1380c27..c1380c27 100644 --- a/src/modules/module-x11-cork-request.c +++ b/src/modules/x11/module-x11-cork-request.c diff --git a/src/modules/module-x11-publish.c b/src/modules/x11/module-x11-publish.c index 2c7fdc12..2c7fdc12 100644 --- a/src/modules/module-x11-publish.c +++ b/src/modules/x11/module-x11-publish.c diff --git a/src/modules/module-x11-xsmp.c b/src/modules/x11/module-x11-xsmp.c index 28fd373a..28fd373a 100644 --- a/src/modules/module-x11-xsmp.c +++ b/src/modules/x11/module-x11-xsmp.c diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c index ce7dadc9..4654a9ad 100644 --- a/src/pulse/channelmap.c +++ b/src/pulse/channelmap.c @@ -839,3 +839,28 @@ const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) { return NULL; } + +int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) { + unsigned c; + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + pa_return_val_if_fail(p < PA_CHANNEL_POSITION_MAX, 0); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == p) + return 1; + + return 0; +} + +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) { + unsigned c; + pa_channel_position_mask_t r = 0; + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + for (c = 0; c < map->channels; c++) + r |= PA_CHANNEL_POSITION_MASK(map->map[c]); + + return r; +} diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h index d4db45b0..2aaead01 100644 --- a/src/pulse/channelmap.h +++ b/src/pulse/channelmap.h @@ -74,26 +74,30 @@ typedef enum pa_channel_position { PA_CHANNEL_POSITION_INVALID = -1, PA_CHANNEL_POSITION_MONO = 0, - PA_CHANNEL_POSITION_LEFT, - PA_CHANNEL_POSITION_RIGHT, - PA_CHANNEL_POSITION_CENTER, + PA_CHANNEL_POSITION_FRONT_LEFT, /* Apple calls this 'Left' */ + PA_CHANNEL_POSITION_FRONT_RIGHT, /* Apple calls this 'Right' */ + PA_CHANNEL_POSITION_FRONT_CENTER, /* Apple calls this '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, +/** \cond fulldocs */ + PA_CHANNEL_POSITION_LEFT = PA_CHANNEL_POSITION_FRONT_LEFT, + PA_CHANNEL_POSITION_RIGHT = PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_CENTER = PA_CHANNEL_POSITION_FRONT_CENTER, +/** \endcond */ - PA_CHANNEL_POSITION_REAR_CENTER, - PA_CHANNEL_POSITION_REAR_LEFT, - PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_REAR_CENTER, /* Microsoft calls this 'Back Center', Apple calls this 'Center Surround' */ + PA_CHANNEL_POSITION_REAR_LEFT, /* Microsoft calls this 'Back Left', Apple calls this 'Left Surround' */ + PA_CHANNEL_POSITION_REAR_RIGHT, /* Microsoft calls this 'Back Right', Apple calls this 'Right Surround' */ - PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_LFE, /* Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */ +/** \cond fulldocs */ PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE, +/** \endcond */ - PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, - PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, /* Apple calls this 'Left Center' */ + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, /* Apple calls this 'Right Center */ - PA_CHANNEL_POSITION_SIDE_LEFT, - PA_CHANNEL_POSITION_SIDE_RIGHT, + PA_CHANNEL_POSITION_SIDE_LEFT, /* Apple calls this 'Left Surround Direct' */ + PA_CHANNEL_POSITION_SIDE_RIGHT, /* Apple calls this 'Right Surround Direct' */ PA_CHANNEL_POSITION_AUX0, PA_CHANNEL_POSITION_AUX1, @@ -128,15 +132,15 @@ typedef enum pa_channel_position { PA_CHANNEL_POSITION_AUX30, PA_CHANNEL_POSITION_AUX31, - PA_CHANNEL_POSITION_TOP_CENTER, + PA_CHANNEL_POSITION_TOP_CENTER, /* Apple calls this 'Top Center Surround' */ - PA_CHANNEL_POSITION_TOP_FRONT_LEFT, - PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, - PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, /* Apple calls this 'Vertical Height Left' */ + PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, /* Apple calls this 'Vertical Height Right' */ + PA_CHANNEL_POSITION_TOP_FRONT_CENTER, /* Apple calls this 'Vertical Height Center' */ - PA_CHANNEL_POSITION_TOP_REAR_LEFT, - PA_CHANNEL_POSITION_TOP_REAR_RIGHT, - PA_CHANNEL_POSITION_TOP_REAR_CENTER, + PA_CHANNEL_POSITION_TOP_REAR_LEFT, /* Microsoft and Apple call this 'Top Back Left' */ + PA_CHANNEL_POSITION_TOP_REAR_RIGHT, /* Microsoft and Apple call this 'Top Back Right' */ + PA_CHANNEL_POSITION_TOP_REAR_CENTER, /* Microsoft and Apple call this 'Top Back Center' */ PA_CHANNEL_POSITION_MAX } pa_channel_position_t; @@ -201,6 +205,12 @@ typedef enum pa_channel_position { #define PA_CHANNEL_POSITION_MAX PA_CHANNEL_POSITION_MAX /** \endcond */ +/** A mask of channel positions. \since 0.9.16 */ +typedef uint64_t pa_channel_position_mask_t; + +/** Makes a bit mask from a channel position. \since 0.9.16 */ +#define PA_CHANNEL_POSITION_MASK(f) ((pa_channel_position_mask_t) (1 << (f))) + /** A list of channel mapping definitions for pa_channel_map_init_auto() */ typedef enum pa_channel_map_def { PA_CHANNEL_MAP_AIFF, @@ -325,6 +335,13 @@ mapping. I.e. "Stereo", "Surround 7.1" and so on. If the channel mapping is unknown NULL will be returned. \since 0.9.15 */ const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) PA_GCC_PURE; +/** Returns TRUE if the specified channel position is available at + * least once in the channel map. \since 0.9.16 */ +int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) PA_GCC_PURE; + +/** Generates a bit mask from a channel map. \since 0.9.16 */ +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) PA_GCC_PURE; + PA_C_DECL_END #endif diff --git a/src/pulse/context.c b/src/pulse/context.c index bfff0e17..3b7bf08d 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -811,10 +811,10 @@ static int try_next_connection(pa_context *c) { #ifdef HAVE_DBUS if (c->no_fail && !c->server_specified) { - if (!c->system_bus) - track_pulseaudio_on_dbus(c, DBUS_BUS_SYSTEM, &c->system_bus); if (!c->session_bus) track_pulseaudio_on_dbus(c, DBUS_BUS_SESSION, &c->session_bus); + if (!c->system_bus) + track_pulseaudio_on_dbus(c, DBUS_BUS_SYSTEM, &c->system_bus); } else #endif pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); @@ -892,7 +892,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo /* FIXME: We probably should check if this is actually the NameOwnerChanged we were looking for */ - is_session = bus == pa_dbus_wrap_connection_get(c->session_bus); + is_session = c->session_bus && bus == pa_dbus_wrap_connection_get(c->session_bus); pa_log_debug("Rock!! PulseAudio is back on %s bus", is_session ? "session" : "system"); if (is_session) diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c index db4c9344..c904f533 100644 --- a/src/pulse/proplist.c +++ b/src/pulse/proplist.c @@ -140,6 +140,21 @@ static int proplist_setn(pa_proplist *p, const char *key, size_t key_length, con return 0; } +/** Will accept only valid UTF-8 */ +int pa_proplist_setp(pa_proplist *p, const char *pair) { + const char *t; + + pa_assert(p); + pa_assert(pair); + + if (!(t = strchr(pair, '='))) + return -1; + + return proplist_setn(p, + pair, t - pair, + t + 1, strchr(pair, 0) - t - 1); +} + static int proplist_sethex(pa_proplist *p, const char *key, size_t key_length, const char *value, size_t value_length) { struct property *prop; pa_bool_t add = FALSE; diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index 2e7e5ad0..4c791dce 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -39,6 +39,12 @@ PA_C_DECL_BEGIN /** For streams: localized media artist if applicable, formatted as UTF-8. e.g. "Guns'N'Roses" */ #define PA_PROP_MEDIA_ARTIST "media.artist" +/** For streams: localized media copyright string if applicable, formatted as UTF-8. e.g. "Evil Record Corp." */ +#define PA_PROP_MEDIA_COPYRIGHT "media.copyright" + +/** For streams: localized media generator software string if applicable, formatted as UTF-8. e.g. "Foocrop AudioFrobnicator" */ +#define PA_PROP_MEDIA_SOFTWARE "media.software" + /** For streams: media language if applicable, in standard POSIX format. e.g. "de_DE" */ #define PA_PROP_MEDIA_LANGUAGE "media.language" @@ -234,6 +240,14 @@ int pa_proplist_sets(pa_proplist *p, const char *key, const char *value); /** Append a new string entry to the property list, possibly * overwriting an already existing entry with the same key. An * internal copy of the data passed is made. Will accept only valid + * UTF-8. The string passed in must contain a '='. Left hand side of + * the '=' is used as key name, the right hand side as string + * data. \since 0.9.16 */ +int pa_proplist_setp(pa_proplist *p, const char *pair); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid * UTF-8. The data can be passed as printf()-style format string with * arguments. \since 0.9.11 */ int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) PA_GCC_PRINTF_ATTR(3,4); diff --git a/src/pulse/sample.c b/src/pulse/sample.c index 1e67b037..0f19f8eb 100644 --- a/src/pulse/sample.c +++ b/src/pulse/sample.c @@ -231,13 +231,46 @@ pa_sample_format_t pa_parse_sample_format(const char *format) { else if (strcasecmp(format, "s24re") == 0) return PA_SAMPLE_S24RE; else if (strcasecmp(format, "s24-32le") == 0) - return PA_SAMPLE_S24LE; + return PA_SAMPLE_S24_32LE; else if (strcasecmp(format, "s24-32be") == 0) - return PA_SAMPLE_S24BE; + return PA_SAMPLE_S24_32BE; else if (strcasecmp(format, "s24-32ne") == 0 || strcasecmp(format, "s24-32") == 0) - return PA_SAMPLE_S24NE; + return PA_SAMPLE_S24_32NE; else if (strcasecmp(format, "s24-32re") == 0) - return PA_SAMPLE_S24RE; + return PA_SAMPLE_S24_32RE; return -1; } + +int pa_sample_format_is_le(pa_sample_format_t f) { + pa_assert(f >= PA_SAMPLE_U8); + pa_assert(f < PA_SAMPLE_MAX); + + switch (f) { + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_FLOAT32LE: + return 1; + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_FLOAT32BE: + return 0; + + default: + return -1; + } +} + +int pa_sample_format_is_be(pa_sample_format_t f) { + int r; + + if ((r = pa_sample_format_is_le(f)) < 0) + return r; + + return !r; +} diff --git a/src/pulse/sample.h b/src/pulse/sample.h index 138f13cf..53d7dea3 100644 --- a/src/pulse/sample.h +++ b/src/pulse/sample.h @@ -305,6 +305,26 @@ char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec); /** Pretty print a byte size value. (i.e. "2.5 MiB") */ char* pa_bytes_snprint(char *s, size_t l, unsigned v); +/** Return 1 when the specified format is little endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +int pa_sample_format_is_le(pa_sample_format_t f) PA_GCC_PURE; + +/** Return 1 when the specified format is big endian, return -1 when + * endianess does not apply to this format. \since 0.9.16 */ +int pa_sample_format_is_be(pa_sample_format_t f) PA_GCC_PURE; + +#ifdef WORDS_BIGENDIAN +#define pa_sample_format_is_ne(f) pa_sample_format_is_be(f) +#define pa_sample_format_is_re(f) pa_sample_format_is_le(f) +#else +/** Return 1 when the specified format is native endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +#define pa_sample_format_is_ne(f) pa_sample_format_is_le(f) +/** Return 1 when the specified format is reverse endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +#define pa_sample_format_is_re(f) pa_sample_format_is_be(f) +#endif + PA_C_DECL_END #endif diff --git a/src/pulse/simple.c b/src/pulse/simple.c index e70b7b1f..f4481fc3 100644 --- a/src/pulse/simple.c +++ b/src/pulse/simple.c @@ -50,35 +50,38 @@ struct pa_simple { int operation_success; }; -#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) do { \ -if (!(expression)) { \ - if (rerror) \ - *(rerror) = error; \ - return (ret); \ - } \ -} while(0); - -#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) do { \ -if (!(expression)) { \ - if (rerror) \ - *(rerror) = pa_context_errno((p)->context); \ - goto label; \ - } \ -} while(0); - -#define CHECK_DEAD_GOTO(p, rerror, label) do { \ -if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ - !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \ - if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ - ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ - if (rerror) \ - *(rerror) = pa_context_errno((p)->context); \ - } else \ - if (rerror) \ - *(rerror) = PA_ERR_BADSTATE; \ - goto label; \ - } \ -} while(0); +#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = error; \ + return (ret); \ + } \ + } while(FALSE); + +#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + goto label; \ + } \ + } while(FALSE); + +#define CHECK_DEAD_GOTO(p, rerror, label) \ + do { \ + if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ + !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \ + if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ + ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + } else \ + if (rerror) \ + *(rerror) = PA_ERR_BADSTATE; \ + goto label; \ + } \ + } while(FALSE); static void context_state_cb(pa_context *c, void *userdata) { pa_simple *p = userdata; @@ -198,9 +201,15 @@ pa_simple* pa_simple_new( pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p); if (dir == PA_STREAM_PLAYBACK) - r = pa_stream_connect_playback(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + r = pa_stream_connect_playback(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); else - r = pa_stream_connect_record(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE); + r = pa_stream_connect_record(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE); if (r < 0) { error = pa_context_errno(p->context); diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in index 566dd55e..3143e98e 100644 --- a/src/pulse/version.h.in +++ b/src/pulse/version.h.in @@ -60,6 +60,13 @@ const char* pa_get_library_version(void); /** The micro version of PA. \since 0.9.15 */ #define PA_MICRO @PA_MICRO@ +/** Evaluates to TRUE if the PulseAudio library version is equal or + * newer than the specified. \since 0.9.16 */ +#define PA_CHECK_VERSION(major,minor,micro) \ + ((PA_MAJOR > (major)) || \ + (PA_MAJOR == (major) && CA_MINOR > (minor)) || \ + (PA_MAJOR == (major) && CA_MINOR == (minor) && CA_MICRO >= (micro))) + PA_C_DECL_END #endif diff --git a/src/pulse/volume.c b/src/pulse/volume.c index 6848771e..64688e0b 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -80,29 +80,78 @@ 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; + unsigned c; pa_assert(a); pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); - for (i = 0; i < a->channels; i++) - sum += a->values[i]; + for (c = 0; c < a->channels; c++) + sum += a->values[c]; sum /= a->channels; return (pa_volume_t) sum; } +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + uint64_t sum = 0; + unsigned c, n; + + pa_assert(a); + + if (!cm) + return pa_cvolume_avg(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = n = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + sum += a->values[c]; + n ++; + } + + if (n > 0) + sum /= n; + + return (pa_volume_t) sum; +} + pa_volume_t pa_cvolume_max(const pa_cvolume *a) { pa_volume_t m = 0; - int i; + unsigned c; pa_assert(a); pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); - for (i = 0; i < a->channels; i++) - if (a->values[i] > m) - m = a->values[i]; + for (c = 0; c < a->channels; c++) + if (a->values[c] > m) + m = a->values[c]; + + return m; +} + +pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + pa_volume_t m = 0; + unsigned c, n; + + pa_assert(a); + + if (!cm) + return pa_cvolume_max(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = n = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + if (a->values[c] > m) + m = a->values[c]; + } return m; } @@ -120,20 +169,28 @@ pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) { return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) / v); } -#define USER_DECIBEL_RANGE 90 +/* Amplitude, not power */ +static double linear_to_dB(double v) { + return 20.0 * log10(v); +} + +static double dB_to_linear(double v) { + return pow(10.0, v / 20.0); +} pa_volume_t pa_sw_volume_from_dB(double dB) { - if (isinf(dB) < 0 || dB <= -USER_DECIBEL_RANGE) + if (isinf(dB) < 0 || dB <= PA_DECIBEL_MININFTY) return PA_VOLUME_MUTED; - return (pa_volume_t) lrint(ceil((dB/USER_DECIBEL_RANGE+1.0)*PA_VOLUME_NORM)); + return pa_sw_volume_from_linear(dB_to_linear(dB)); } double pa_sw_volume_to_dB(pa_volume_t v) { - if (v == PA_VOLUME_MUTED) + + if (v <= PA_VOLUME_MUTED) return PA_DECIBEL_MININFTY; - return ((double) v/PA_VOLUME_NORM-1)*USER_DECIBEL_RANGE; + return linear_to_dB(pa_sw_volume_to_linear(v)); } pa_volume_t pa_sw_volume_from_linear(double v) { @@ -141,18 +198,28 @@ pa_volume_t pa_sw_volume_from_linear(double v) { if (v <= 0.0) return PA_VOLUME_MUTED; - if (v > .999 && v < 1.001) - return PA_VOLUME_NORM; + /* + * We use a cubic mapping here, as suggested and discussed here: + * + * http://www.robotplanet.dk/audio/audio_gui_design/ + * http://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#23151 + */ - return pa_sw_volume_from_dB(20.0*log10(v)); + return (pa_volume_t) (cbrt(v) * PA_VOLUME_NORM); } double pa_sw_volume_to_linear(pa_volume_t v) { + double f; - if (v == PA_VOLUME_MUTED) + if (v <= PA_VOLUME_MUTED) return 0.0; - return pow(10.0, pa_sw_volume_to_dB(v)/20.0); + if (v == PA_VOLUME_NORM) + return 1.0; + + f = ((double) v / PA_VOLUME_NORM); + + return f*f*f; } char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { @@ -225,7 +292,7 @@ char *pa_sw_cvolume_snprint_dB(char *s, size_t l, const pa_cvolume *c) { l -= pa_snprintf(e, l, "%s%u: %0.2f dB", first ? "" : " ", channel, - isinf(f) < 0 || f <= -USER_DECIBEL_RANGE ? -INFINITY : f); + isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); e = strchr(e, 0); first = FALSE; @@ -249,7 +316,7 @@ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v) { f = pa_sw_volume_to_dB(v); pa_snprintf(s, l, "%0.2f dB", - isinf(f) < 0 || f <= -USER_DECIBEL_RANGE ? -INFINITY : f); + isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); return s; } @@ -572,11 +639,29 @@ pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max) { pa_return_val_if_fail(pa_cvolume_valid(v), NULL); pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); + t = pa_cvolume_max(v); + + if (t <= PA_VOLUME_MUTED) + return pa_cvolume_set(v, v->channels, max); + for (c = 0; c < v->channels; c++) - if (v->values[c] > t) - t = v->values[c]; + v->values[c] = (pa_volume_t) (((uint64_t) v->values[c] * (uint64_t) max) / (uint64_t) t); + + return v; +} - if (t <= 0) +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map *cm, pa_channel_position_mask_t mask) { + unsigned c; + pa_volume_t t = 0; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); + + t = pa_cvolume_max_mask(v, cm, mask); + + if (t <= PA_VOLUME_MUTED) return pa_cvolume_set(v, v->channels, max); for (c = 0; c < v->channels; c++) @@ -685,3 +770,49 @@ pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float return v; } + +pa_cvolume* pa_cvolume_set_position( + pa_cvolume *cv, + const pa_channel_map *map, + pa_channel_position_t t, + pa_volume_t v) { + + unsigned c; + pa_bool_t good = FALSE; + + pa_assert(cv); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), NULL); + pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, NULL); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == t) { + cv->values[c] = v; + good = TRUE; + } + + return good ? cv : NULL; +} + +pa_volume_t pa_cvolume_get_position( + pa_cvolume *cv, + const pa_channel_map *map, + pa_channel_position_t t) { + + unsigned c; + pa_volume_t v = PA_VOLUME_MUTED; + + pa_assert(cv); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), PA_VOLUME_MUTED); + pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, PA_VOLUME_MUTED); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == t) + if (cv->values[c] > v) + v = cv->values[c]; + + return v; +} diff --git a/src/pulse/volume.h b/src/pulse/volume.h index 5b7e1213..c07fd99a 100644 --- a/src/pulse/volume.h +++ b/src/pulse/volume.h @@ -178,9 +178,23 @@ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v); /** Return the average volume of all channels */ pa_volume_t pa_cvolume_avg(const pa_cvolume *a) PA_GCC_PURE; +/** Return the average volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_avg(). If no + * channel is selected the returned value will be + * PA_VOLUME_MUTED. \since 0.9.16 */ +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + /** Return the maximum volume of all channels. \since 0.9.12 */ pa_volume_t pa_cvolume_max(const pa_cvolume *a) PA_GCC_PURE; +/** Return the maximum volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_max(). If no + * channel is selected the returned value will be PA_VOLUME_MUTED. + * \since 0.9.16 */ +pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + /** Return TRUE when the passed cvolume structure is valid, FALSE otherwise */ int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE; @@ -212,10 +226,10 @@ pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; * *dest. This is only valid for software volumes! \since 0.9.13 */ pa_cvolume *pa_sw_cvolume_divide(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! */ +/** Convert a decibel value to a volume (amplitude, not power). This is only valid for software volumes! */ pa_volume_t pa_sw_volume_from_dB(double f) PA_GCC_CONST; -/** Convert a volume to a decibel value. This is only valid for software volumes! */ +/** Convert a volume to a decibel value (amplitude, not power). This is only valid for software volumes! */ double pa_sw_volume_to_dB(pa_volume_t v) PA_GCC_CONST; /** Convert a linear factor to a volume. This is only valid for software volumes! */ @@ -227,7 +241,7 @@ double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST; #ifdef INFINITY #define PA_DECIBEL_MININFTY ((double) -INFINITY) #else -/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). */ +/** This floor value is used as minus infinity when using pa_volume_{to,from}_dB(). */ #define PA_DECIBEL_MININFTY ((double) -200.0) #endif @@ -283,6 +297,25 @@ pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float * volumes are kept. \since 0.9.15 */ pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max); +/** Scale the passed pa_cvolume structure so that the maximum volume + * of all channels selected via cm/mask equals max. This also modifies + * the volume of those channels that are unmasked. The proportions + * between the channel volumes are kept. \since 0.9.16 */ +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map *cm, pa_channel_position_mask_t mask); + +/** Set the passed volume to all channels at the specified channel + * position. Will return the updated volume struct, or NULL if there + * is no channel at the position specified. You can check if a channel + * map includes a specific position by calling + * pa_channel_map_has_position(). \since 0.9.16 */ +pa_cvolume* pa_cvolume_set_position(pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t, pa_volume_t v); + +/** Get the maximum volume of all channels at the specified channel + * position. Will return 0 if there is no channel at the position + * specified. You can check if a channel map includes a specific + * position by calling pa_channel_map_has_position(). \since 0.9.16 */ +pa_volume_t pa_cvolume_get_position(pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t) PA_GCC_PURE; + PA_C_DECL_END #endif diff --git a/src/pulsecore/aupdate.c b/src/pulsecore/aupdate.c new file mode 100644 index 00000000..56ebb8e5 --- /dev/null +++ b/src/pulsecore/aupdate.c @@ -0,0 +1,129 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to 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/semaphore.h> +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> + +#include "aupdate.h" + +#define MSB (1U << (sizeof(unsigned)*8U-1)) +#define WHICH(n) (!!((n) & MSB)) +#define COUNTER(n) ((n) & ~MSB) + +struct pa_aupdate { + pa_atomic_t read_lock; + pa_mutex *write_lock; + pa_semaphore *semaphore; +}; + +pa_aupdate *pa_aupdate_new(void) { + pa_aupdate *a; + + a = pa_xnew(pa_aupdate, 1); + pa_atomic_store(&a->read_lock, 0); + a->write_lock = pa_mutex_new(FALSE, FALSE); + a->semaphore = pa_semaphore_new(0); + + return a; +} + +void pa_aupdate_free(pa_aupdate *a) { + pa_assert(a); + + pa_mutex_free(a->write_lock); + pa_semaphore_free(a->semaphore); + + pa_xfree(a); +} + +unsigned pa_aupdate_read_begin(pa_aupdate *a) { + unsigned n; + + pa_assert(a); + + /* Increase the lock counter */ + n = (unsigned) pa_atomic_inc(&a->read_lock); + + /* When n is 0 we have about 2^31 threads running that all try to + * access the data at the same time, oh my! */ + pa_assert(COUNTER(n)+1 > 0); + + /* The uppermost bit tells us which data to look at */ + return WHICH(n); +} + +void pa_aupdate_read_end(pa_aupdate *a) { + unsigned n; + + pa_assert(a); + + /* Decrease the lock counter */ + n = (unsigned) pa_atomic_dec(&a->read_lock); + + /* Make sure the counter was valid */ + pa_assert(COUNTER(n) > 0); + + /* Post the semaphore */ + pa_semaphore_post(a->semaphore); +} + +unsigned pa_aupdate_write_begin(pa_aupdate *a) { + unsigned n; + + pa_assert(a); + + pa_mutex_lock(a->write_lock); + + n = (unsigned) pa_atomic_load(&a->read_lock); + + return !WHICH(n); +} + +unsigned pa_aupdate_write_swap(pa_aupdate *a) { + unsigned n; + + pa_assert(a); + + for (;;) { + n = (unsigned) pa_atomic_load(&a->read_lock); + + /* If the read counter is > 0 wait; if it is 0 try to swap the lists */ + if (COUNTER(n) > 0) + pa_semaphore_wait(a->semaphore); + else if (pa_atomic_cmpxchg(&a->read_lock, (int) n, (int) (n ^ MSB))) + break; + } + + return WHICH(n); +} + +void pa_aupdate_write_end(pa_aupdate *a) { + pa_assert(a); + + pa_mutex_unlock(a->write_lock); +} diff --git a/src/pulsecore/aupdate.h b/src/pulsecore/aupdate.h new file mode 100644 index 00000000..072e382d --- /dev/null +++ b/src/pulsecore/aupdate.h @@ -0,0 +1,98 @@ +#ifndef foopulsecoreaupdatehfoo +#define foopulsecoreaupdatehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct pa_aupdate pa_aupdate; + +pa_aupdate *pa_aupdate_new(void); +void pa_aupdate_free(pa_aupdate *a); + +/* Will return 0, or 1, depending on which copy of the data the caller + * should look at */ +unsigned pa_aupdate_read_begin(pa_aupdate *a); +void pa_aupdate_read_end(pa_aupdate *a); + +/* Will return 0, or 1, depending which copy of the data the caller + * should modify */ +unsigned pa_aupdate_write_begin(pa_aupdate *a); +void pa_aupdate_write_end(pa_aupdate *a); + +/* Will return 0, or 1, depending which copy of the data the caller + * should modify. Each time called this will return the opposite of + * the previous pa_aupdate_write_begin()/pa_aupdate_write_swap() + * call. Should only be called between pa_aupdate_write_begin() and + * pa_aupdate_write_end() */ +unsigned pa_aupdate_write_swap(pa_aupdate *a); + +/* + * This infrastructure allows lock-free updates of arbitrary data + * structures in an rcu'ish way: two copies of the data structure + * should be exisiting. One side ('the reader') has read access to one + * of the two data structure at a time. It does not have to lock it, + * however it needs to signal that it is using it/stopped using + * it. The other side ('the writer') modifes the second data structure, + * and then atomically swaps the two data structures, followed by a + * modification of the other one. + * + * This is intended to be used for cases where the reader side needs + * to be fast while the writer side can be slow. + * + * The reader side is signal handler safe. + * + * The writer side lock is not recursive. The reader side is. + * + * There may be multiple readers and multiple writers at the same + * time. + * + * Usage is like this: + * + * static struct foo bar[2]; + * static pa_aupdate *a; + * + * reader() { + * unsigned j; + * + * j = pa_update_read_begin(a); + * + * ... read the data structure bar[j] ... + * + * pa_update_read_end(a); + * } + * + * writer() { + * unsigned j; + * + * j = pa_update_write_begin(a); + * + * ... update the data structure bar[j] ... + * + * j = pa_update_write_swap(a); + * + * ... update the data structure bar[j], the same way as above ... + * + * pa_update_write_end(a) + * } + * + */ + +#endif diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c index 8101a92e..59b8cda6 100644 --- a/src/pulsecore/card.c +++ b/src/pulsecore/card.c @@ -244,19 +244,20 @@ int pa_card_set_profile(pa_card *c, const char *name, pa_bool_t save) { return 0; } -int pa_card_suspend(pa_card *c, pa_bool_t suspend) { +int pa_card_suspend(pa_card *c, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_sink *sink; pa_source *source; uint32_t idx; int ret = 0; pa_assert(c); + pa_assert(cause != 0); for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) - ret -= pa_sink_suspend(sink, suspend) < 0; + ret -= pa_sink_suspend(sink, suspend, cause) < 0; for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) - ret -= pa_source_suspend(source, suspend) < 0; + ret -= pa_source_suspend(source, suspend, cause) < 0; return ret; } diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h index 3b7608f6..415ab678 100644 --- a/src/pulsecore/card.h +++ b/src/pulsecore/card.h @@ -99,6 +99,6 @@ void pa_card_free(pa_card *c); int pa_card_set_profile(pa_card *c, const char *name, pa_bool_t save); -int pa_card_suspend(pa_card *c, pa_bool_t suspend); +int pa_card_suspend(pa_card *c, pa_bool_t suspend, pa_suspend_cause_t cause); #endif diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index 15fe525c..644de96e 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -483,6 +483,8 @@ static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, if (i->usage) pa_strbuf_printf(buf, "Usage: %s\n", i->usage); pa_strbuf_printf(buf, "Load Once: %s\n", pa_yes_no(i->load_once)); + if (i->deprecated) + pa_strbuf_printf(buf, "Warning, deprecated: %s\n", i->deprecated); } pa_modinfo_free(i); @@ -1276,7 +1278,7 @@ static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *b return -1; } - if ((r = pa_sink_suspend(sink, suspend)) < 0) + if ((r = pa_sink_suspend(sink, suspend, PA_SUSPEND_USER)) < 0) pa_strbuf_printf(buf, "Failed to resume/suspend sink: %s\n", pa_strerror(r)); return 0; @@ -1312,7 +1314,7 @@ static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf return -1; } - if ((r = pa_source_suspend(source, suspend)) < 0) + if ((r = pa_source_suspend(source, suspend, PA_SUSPEND_USER)) < 0) pa_strbuf_printf(buf, "Failed to resume/suspend source: %s\n", pa_strerror(r)); return 0; @@ -1337,10 +1339,10 @@ static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, p return -1; } - if ((r = pa_sink_suspend_all(c, suspend)) < 0) + if ((r = pa_sink_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0) pa_strbuf_printf(buf, "Failed to resume/suspend all sinks: %s\n", pa_strerror(r)); - if ((r = pa_source_suspend_all(c, suspend)) < 0) + if ((r = pa_source_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0) pa_strbuf_printf(buf, "Failed to resume/suspend all sources: %s\n", pa_strerror(r)); return 0; diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index 604678be..bc863f05 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -232,6 +232,7 @@ char *pa_sink_list_to_string(pa_core *c) { "\tdriver: <%s>\n" "\tflags: %s%s%s%s%s%s%s%s\n" "\tstate: %s\n" + "\tsuspend cause: %s%s%s%s\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" "\tbase volume: %s%s%s\n" @@ -258,6 +259,10 @@ char *pa_sink_list_to_string(pa_core *c) { sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "", sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", sink_state_to_string(pa_sink_get_state(sink)), + sink->suspend_cause & PA_SUSPEND_USER ? "USER " : "", + sink->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "", + sink->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "", + sink->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "", pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE, FALSE)), sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t " : "", sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_get_volume(sink, FALSE, FALSE)) : "", @@ -335,6 +340,7 @@ char *pa_source_list_to_string(pa_core *c) { "\tdriver: <%s>\n" "\tflags: %s%s%s%s%s%s%s\n" "\tstate: %s\n" + "\tsuspend cause: %s%s%s%s\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" "\tbase volume: %s%s%s\n" @@ -358,6 +364,10 @@ char *pa_source_list_to_string(pa_core *c) { source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", source_state_to_string(pa_source_get_state(source)), + source->suspend_cause & PA_SUSPEND_USER ? "USER " : "", + source->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "", + source->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "", + source->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "", pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source, FALSE)), source->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t " : "", source->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_source_get_volume(source, FALSE)) : "", diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c index a784f583..54514e7f 100644 --- a/src/pulsecore/cli.c +++ b/src/pulsecore/cli.c @@ -59,6 +59,8 @@ struct pa_cli { pa_bool_t fail, kill_requested; int defer_kill; + + char *last_line; }; static void line_callback(pa_ioline *line, const char *s, void *userdata); @@ -101,6 +103,8 @@ pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) { c->fail = c->kill_requested = FALSE; c->defer_kill = 0; + c->last_line = NULL; + return c; } @@ -110,6 +114,7 @@ void pa_cli_free(pa_cli *c) { pa_ioline_close(c->line); pa_ioline_unref(c->line); pa_client_free(c->client); + pa_xfree(c->last_line); pa_xfree(c); } @@ -144,6 +149,14 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { return; } + /* Magic command, like they had in AT Hayes Modems! Those were the good days! */ + if (pa_streq(s, "/")) + s = c->last_line; + else if (s[0]) { + pa_xfree(c->last_line); + c->last_line = pa_xstrdup(s); + } + pa_assert_se(buf = pa_strbuf_new()); c->defer_kill++; pa_cli_command_execute_line(c->core, s, buf, &c->fail); diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c index 34d60a8f..086f5fcb 100644 --- a/src/pulsecore/core-scache.c +++ b/src/pulsecore/core-scache.c @@ -219,11 +219,14 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3 pa_assert(name); pa_assert(filename); - if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk) < 0) - return -1; - p = pa_proplist_new(); pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename); + + if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk, p) < 0) { + pa_proplist_free(p); + return -1; + } + r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx); pa_memblock_unref(chunk.memblock); pa_proplist_free(p); @@ -311,11 +314,14 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) return -1; + merged = pa_proplist_new(); + pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name); + if (e->lazy && !e->memchunk.memblock) { pa_channel_map old_channel_map = e->channel_map; - if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk) < 0) - return -1; + if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, merged) < 0) + goto fail; pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); @@ -328,7 +334,7 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t } if (!e->memchunk.memblock) - return -1; + goto fail; pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name); @@ -344,17 +350,13 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t else pass_volume = FALSE; - 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, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) { - pa_proplist_free(merged); - return -1; - } + if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) + goto fail; pa_proplist_free(merged); @@ -362,6 +364,10 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t time(&e->last_used_time); return 0; + +fail: + pa_proplist_free(merged); + return -1; } int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 294f63cb..b747cd84 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -2235,7 +2235,7 @@ int pa_close_all(int except_fd, ...) { int pa_close_allv(const int except_fds[]) { struct rlimit rl; - int fd; + int maxfd, fd; int saved_errno; #ifdef __linux__ @@ -2302,10 +2302,12 @@ int pa_close_allv(const int except_fds[]) { #endif - if (getrlimit(RLIMIT_NOFILE, &rl) < 0) - return -1; + if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) + maxfd = (int) rl.rlim_max; + else + maxfd = sysconf(_SC_OPEN_MAX); - for (fd = 3; fd < (int) rl.rlim_max; fd++) { + for (fd = 3; fd < maxfd; fd++) { int i; pa_bool_t found; @@ -2467,31 +2469,29 @@ pa_bool_t pa_in_system_mode(void) { return !!atoi(e); } -char *pa_machine_id(void) { - FILE *f; - size_t l; +char *pa_get_user_name_malloc(void) { + ssize_t k; + char *u; - /* The returned value is supposed be some kind of ascii identifier - * that is unique and stable across reboots. */ +#ifdef _SC_LOGIN_NAME_MAX + k = (ssize_t) sysconf(_SC_LOGIN_NAME_MAX); - /* First we try the D-Bus UUID, which is the best option we have, - * since it fits perfectly our needs and is not as volatile as the - * hostname which might be set from dhcp. */ - - if ((f = fopen(PA_MACHINE_ID, "r"))) { - char ln[34] = "", *r; + if (k <= 0) +#endif + k = 32; - r = fgets(ln, sizeof(ln)-1, f); - fclose(f); + u = pa_xnew(char, k+1); - pa_strip_nl(ln); - - if (r && ln[0]) - return pa_utf8_filter(ln); + if (!(pa_get_user_name(u, k))) { + pa_xfree(u); + return NULL; } - /* The we fall back to the host name. It supposed to be somewhat - * unique, at least in a network, but may change. */ + return u; +} + +char *pa_get_host_name_malloc(void) { + size_t l; l = 100; for (;;) { @@ -2525,6 +2525,35 @@ char *pa_machine_id(void) { l *= 2; } + return NULL; +} + +char *pa_machine_id(void) { + FILE *f; + char *h; + + /* The returned value is supposed be some kind of ascii identifier + * that is unique and stable across reboots. */ + + /* First we try the D-Bus UUID, which is the best option we have, + * since it fits perfectly our needs and is not as volatile as the + * hostname which might be set from dhcp. */ + + if ((f = fopen(PA_MACHINE_ID, "r"))) { + char ln[34] = "", *r; + + r = fgets(ln, sizeof(ln)-1, f); + fclose(f); + + pa_strip_nl(ln); + + if (r && ln[0]) + return pa_utf8_filter(ln); + } + + if ((h = pa_get_host_name_malloc())) + return h; + /* If no hostname was set we use the POSIX hostid. It's usually * the IPv4 address. Might not be that stable. */ return pa_sprintf_malloc("%08lx", (unsigned long) gethostid); @@ -2682,3 +2711,24 @@ char *pa_realpath(const char *path) { return t; } + +void pa_disable_sigpipe(void) { + +#ifdef SIGPIPE + struct sigaction sa; + + pa_zero(sa); + + if (sigaction(SIGPIPE, NULL, &sa) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } + + sa.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } +#endif +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index f96fa443..d073b750 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -201,6 +201,9 @@ pa_bool_t pa_in_system_mode(void); #define pa_streq(a,b) (!strcmp((a),(b))) +char *pa_get_host_name_malloc(void); +char *pa_get_user_name_malloc(void); + char *pa_machine_id(void); char *pa_session_id(void); char *pa_uname_string(void); @@ -224,4 +227,6 @@ char *pa_unescape(char *p); char *pa_realpath(const char *path); +void pa_disable_sigpipe(void); + #endif diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index c6794445..09a880c4 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -27,6 +27,16 @@ typedef struct pa_core pa_core; +/* This is a bitmask that encodes the cause why a sink/source is + * suspended. */ +typedef enum pa_suspend_cause { + PA_SUSPEND_USER = 1, /* Exposed to the user via some protocol */ + PA_SUSPEND_APPLICATION = 2, /* Used by the device reservation logic */ + PA_SUSPEND_IDLE = 4, /* Used by module-suspend-on-idle */ + PA_SUSPEND_SESSION = 8, /* Used by module-hal for mark inactive sessions */ + PA_SUSPEND_ALL = 0xFFFF /* Magic cause that can be used to resume forcibly */ +} pa_suspend_cause_t; + #include <pulsecore/idxset.h> #include <pulsecore/hashmap.h> #include <pulsecore/memblock.h> diff --git a/src/pulsecore/database-gdbm.c b/src/pulsecore/database-gdbm.c new file mode 100644 index 00000000..aeaac64b --- /dev/null +++ b/src/pulsecore/database-gdbm.c @@ -0,0 +1,246 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to 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 <gdbm.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "database.h" + +#define MAKE_GDBM_FILE(x) ((GDBM_FILE) (x)) + +static inline datum* datum_to_gdbm(datum *to, const pa_datum *from) { + pa_assert(from); + pa_assert(to); + + to->dptr = from->data; + to->dsize = from->size; + + return to; +} + +static inline pa_datum* datum_from_gdbm(pa_datum *to, const datum *from) { + pa_assert(from); + pa_assert(to); + + to->data = from->dptr; + to->size = from->dsize; + + return to; +} + +void pa_datum_free(pa_datum *d) { + pa_assert(d); + + free(d->data); /* gdbm uses raw malloc/free hence we should do that here, too */ + pa_zero(d); +} + +pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { + GDBM_FILE f; + int gdbm_cache_size; + char *path; + + pa_assert(fn); + + /* We include the host identifier in the file name because gdbm + * files are CPU dependant, and we don't want things to go wrong + * if we are on a multiarch system. */ + + path = pa_sprintf_malloc("%s."CANONICAL_HOST".gdbm", fn); + errno = 0; + f = gdbm_open((char*) path, 0, GDBM_NOLOCK | (for_write ? GDBM_WRCREAT : GDBM_READER), 0644, NULL); + + if (f) + pa_log_debug("Opened GDBM database '%s'", path); + + pa_xfree(path); + + if (!f) { + if (errno == 0) + errno = EIO; + return NULL; + } + + /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ + gdbm_cache_size = 10; + gdbm_setopt(f, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); + + return (pa_database*) f; +} + +void pa_database_close(pa_database *db) { + pa_assert(db); + + gdbm_close(MAKE_GDBM_FILE(db)); +} + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)); + + return gdbm_data.dptr ? + datum_from_gdbm(data, &gdbm_data) : + NULL; +} + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + return gdbm_store(MAKE_GDBM_FILE(db), + *datum_to_gdbm(&gdbm_key, key), + *datum_to_gdbm(&gdbm_data, data), + overwrite ? GDBM_REPLACE : GDBM_INSERT) != 0 ? -1 : 0; +} + +int pa_database_unset(pa_database *db, const pa_datum *key) { + datum gdbm_key; + + pa_assert(db); + pa_assert(key); + + return gdbm_delete(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)) != 0 ? -1 : 0; +} + +int pa_database_clear(pa_database *db) { + datum gdbm_key; + + pa_assert(db); + + gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + + while (gdbm_key.dptr) { + datum next; + + next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key); + + gdbm_delete(MAKE_GDBM_FILE(db), gdbm_key); + + free(gdbm_key.dptr); + gdbm_key = next; + } + + return gdbm_reorganize(MAKE_GDBM_FILE(db)) == 0 ? 0 : -1; +} + +signed pa_database_size(pa_database *db) { + datum gdbm_key; + unsigned n = 0; + + pa_assert(db); + + /* This sucks */ + + gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + + while (gdbm_key.dptr) { + datum next; + + n++; + + next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key); + free(gdbm_key.dptr); + gdbm_key = next; + } + + return (signed) n; +} + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + + gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + + if (!gdbm_key.dptr) + return NULL; + + if (data) { + gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key); + + if (!gdbm_data.dptr) { + free(gdbm_key.dptr); + return NULL; + } + + datum_from_gdbm(data, &gdbm_data); + } + + datum_from_gdbm(key, &gdbm_key); + + return key; +} + +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + pa_assert(next); + + if (!key) + return pa_database_first(db, next, data); + + gdbm_key = gdbm_nextkey(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)); + + if (!gdbm_key.dptr) + return NULL; + + if (data) { + gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key); + + if (!gdbm_data.dptr) { + free(gdbm_key.dptr); + return NULL; + } + + datum_from_gdbm(data, &gdbm_data); + } + + datum_from_gdbm(next, &gdbm_key); + + return next; +} + +int pa_database_sync(pa_database *db) { + pa_assert(db); + + gdbm_sync(MAKE_GDBM_FILE(db)); + return 0; +} diff --git a/src/pulsecore/database-tdb.c b/src/pulsecore/database-tdb.c new file mode 100644 index 00000000..b79d2837 --- /dev/null +++ b/src/pulsecore/database-tdb.c @@ -0,0 +1,227 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +/* Some versions of tdb lack inclusion of signal.h in the header files but use sigatomic_t */ +#include <signal.h> +#include <tdb.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "database.h" + +#define MAKE_TDB_CONTEXT(x) ((struct tdb_context*) (x)) + +static inline TDB_DATA* datum_to_tdb(TDB_DATA *to, const pa_datum *from) { + pa_assert(from); + pa_assert(to); + + to->dptr = from->data; + to->dsize = from->size; + + return to; +} + +static inline pa_datum* datum_from_tdb(pa_datum *to, const TDB_DATA *from) { + pa_assert(from); + pa_assert(to); + + to->data = from->dptr; + to->size = from->dsize; + + return to; +} + +void pa_datum_free(pa_datum *d) { + pa_assert(d); + + free(d->data); /* tdb uses raw malloc/free hence we should do that here, too */ + pa_zero(d); +} + +pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { + struct tdb_context *c; + char *path; + + pa_assert(fn); + + path = pa_sprintf_malloc("%s.tdb", fn); + errno = 0; + c = tdb_open(path, 0, TDB_NOSYNC|TDB_NOLOCK, + (for_write ? O_RDWR|O_CREAT : O_RDONLY)|O_NOCTTY +#ifdef O_CLOEXEC + |O_CLOEXEC +#endif + , 0644); + + if (c) + pa_log_debug("Opened TDB database '%s'", path); + + pa_xfree(path); + + if (!c) { + if (errno == 0) + errno = EIO; + return NULL; + } + + return (pa_database*) c; +} + +void pa_database_close(pa_database *db) { + pa_assert(db); + + tdb_close(MAKE_TDB_CONTEXT(db)); +} + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)); + + return tdb_data.dptr ? + datum_from_tdb(data, &tdb_data) : + NULL; +} + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + return tdb_store(MAKE_TDB_CONTEXT(db), + *datum_to_tdb(&tdb_key, key), + *datum_to_tdb(&tdb_data, data), + overwrite ? TDB_REPLACE : TDB_INSERT) != 0 ? -1 : 0; +} + +int pa_database_unset(pa_database *db, const pa_datum *key) { + TDB_DATA tdb_key; + + pa_assert(db); + pa_assert(key); + + return tdb_delete(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)) != 0 ? -1 : 0; +} + +int pa_database_clear(pa_database *db) { + pa_assert(db); + + return tdb_wipe_all(MAKE_TDB_CONTEXT(db)) != 0 ? -1 : 0; +} + +signed pa_database_size(pa_database *db) { + TDB_DATA tdb_key; + unsigned n = 0; + + pa_assert(db); + + /* This sucks */ + + tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db)); + + while (tdb_key.dptr) { + TDB_DATA next; + + n++; + + next = tdb_nextkey(MAKE_TDB_CONTEXT(db), tdb_key); + free(tdb_key.dptr); + tdb_key = next; + } + + return (signed) n; +} + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + + tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db)); + + if (!tdb_key.dptr) + return NULL; + + if (data) { + tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key); + + if (!tdb_data.dptr) { + free(tdb_key.dptr); + return NULL; + } + + datum_from_tdb(data, &tdb_data); + } + + datum_from_tdb(key, &tdb_key); + + return key; +} + +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + + tdb_key = tdb_nextkey(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)); + + if (!tdb_key.dptr) + return NULL; + + if (data) { + tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key); + + if (!tdb_data.dptr) { + free(tdb_key.dptr); + return NULL; + } + + datum_from_tdb(data, &tdb_data); + } + + datum_from_tdb(next, &tdb_key); + + return next; +} + +int pa_database_sync(pa_database *db) { + pa_assert(db); + + return 0; +} diff --git a/src/pulsecore/database.h b/src/pulsecore/database.h new file mode 100644 index 00000000..17455d4c --- /dev/null +++ b/src/pulsecore/database.h @@ -0,0 +1,61 @@ +#ifndef foopulsecoredatabasehfoo +#define foopulsecoredatabasehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <sys/types.h> + +#include <pulsecore/macro.h> + +/* A little abstraction over simple databases, such as gdbm, tdb, and + * so on. We only make minimal assumptions about the supported + * backend: it does not need to support locking, it does not have to + * be arch independant. */ + +typedef struct pa_database pa_database; + +typedef struct pa_datum { + void *data; + size_t size; +} pa_datum; + +void pa_datum_free(pa_datum *d); + +/* This will append a suffix to the filename */ +pa_database* pa_database_open(const char *fn, pa_bool_t for_write); +void pa_database_close(pa_database *db); + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data); + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite); +int pa_database_unset(pa_database *db, const pa_datum *key); + +int pa_database_clear(pa_database *db); + +signed pa_database_size(pa_database *db); + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data /* may be NULL */); +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data /* may be NULL */); + +int pa_database_sync(pa_database *db); + +#endif diff --git a/src/pulsecore/endianmacros.h b/src/pulsecore/endianmacros.h index 22579376..2b18cf8d 100644 --- a/src/pulsecore/endianmacros.h +++ b/src/pulsecore/endianmacros.h @@ -45,27 +45,27 @@ #define PA_UINT32_SWAP(x) ( (uint32_t) ( ((uint32_t) (x) >> 24) | ((uint32_t) (x) << 24) | (((uint32_t) (x) & 0xFF00) << 8) | ((((uint32_t) (x)) >> 8) & 0xFF00) ) ) #endif -static inline uint32_t PA_READ24LE(const uint8_t *p) { +static inline uint32_t PA_READ24BE(const uint8_t *p) { return ((uint32_t) p[0] << 16) | ((uint32_t) p[1] << 8) | ((uint32_t) p[2]); } -static inline uint32_t PA_READ24BE(const uint8_t *p) { +static inline uint32_t PA_READ24LE(const uint8_t *p) { return ((uint32_t) p[2] << 16) | ((uint32_t) p[1] << 8) | ((uint32_t) p[0]); } -static inline void PA_WRITE24LE(uint8_t *p, uint32_t u) { +static inline void PA_WRITE24BE(uint8_t *p, uint32_t u) { p[0] = (uint8_t) (u >> 16); p[1] = (uint8_t) (u >> 8); p[2] = (uint8_t) u; } -static inline void PA_WRITE24BE(uint8_t *p, uint32_t u) { +static inline void PA_WRITE24LE(uint8_t *p, uint32_t u) { p[2] = (uint8_t) (u >> 16); p[1] = (uint8_t) (u >> 8); p[0] = (uint8_t) u; diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h index 08e18ead..828e2448 100644 --- a/src/pulsecore/hashmap.h +++ b/src/pulsecore/hashmap.h @@ -65,4 +65,8 @@ void *pa_hashmap_steal_first(pa_hashmap *h); /* Return the oldest entry in the hashmap */ void* pa_hashmap_first(pa_hashmap *h); +/* A macro to ease iteration through all entries */ +#define PA_HASHMAP_FOREACH(e, h, state) \ + for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), NULL); (e); (e) = pa_hashmap_iterate((h), &(state), NULL)) + #endif diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h index 7531ea32..a6179fcf 100644 --- a/src/pulsecore/idxset.h +++ b/src/pulsecore/idxset.h @@ -103,4 +103,8 @@ unsigned pa_idxset_size(pa_idxset*s); /* Return TRUE of the idxset is empty */ pa_bool_t pa_idxset_isempty(pa_idxset *s); +/* A macro to ease iteration through all entries */ +#define PA_IDXSET_FOREACH(e, s, idx) \ + for ((e) = pa_idxset_first((s), &(idx)); (e); (e) = pa_idxset_next((s), &(idx))) + #endif diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c index 5c38d6e5..7afdb08c 100644 --- a/src/pulsecore/ioline.c +++ b/src/pulsecore/ioline.c @@ -57,6 +57,9 @@ struct pa_ioline { pa_ioline_cb_t callback; void *userdata; + pa_ioline_drain_cb_t drain_callback; + void *drain_userdata; + pa_bool_t dead:1; pa_bool_t defer_close:1; }; @@ -81,6 +84,9 @@ pa_ioline* pa_ioline_new(pa_iochannel *io) { l->callback = NULL; l->userdata = NULL; + l->drain_callback = NULL; + l->drain_userdata = NULL; + l->mainloop = pa_iochannel_get_mainloop_api(io); l->defer_event = l->mainloop->defer_new(l->mainloop, defer_callback, l); @@ -202,6 +208,17 @@ void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata l->userdata = userdata; } +void pa_ioline_set_drain_callback(pa_ioline*l, pa_ioline_drain_cb_t callback, void *userdata) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + if (l->dead) + return; + + l->drain_callback = callback; + l->drain_userdata = userdata; +} + static void failure(pa_ioline *l, pa_bool_t process_leftover) { pa_assert(l); pa_assert(PA_REFCNT_VALUE(l) >= 1); @@ -266,7 +283,7 @@ static int do_read(pa_ioline *l) { pa_assert(l); pa_assert(PA_REFCNT_VALUE(l) >= 1); - while (!l->dead && pa_iochannel_is_readable(l->io)) { + while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) { ssize_t r; size_t len; @@ -331,12 +348,12 @@ static int do_write(pa_ioline *l) { pa_assert(l); pa_assert(PA_REFCNT_VALUE(l) >= 1); - while (!l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length) { + while (l->io && !l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length > 0) { if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) <= 0) { if (r < 0 && errno == EAGAIN) - return 0; + break; if (r < 0 && errno != EPIPE) pa_log("write(): %s", pa_cstrerror(errno)); @@ -354,6 +371,9 @@ static int do_write(pa_ioline *l) { l->wbuf_index = 0; } + if (l->wbuf_valid_length <= 0 && l->drain_callback) + l->drain_callback(l, l->drain_userdata); + return 0; } @@ -423,3 +443,25 @@ void pa_ioline_printf(pa_ioline *l, const char *format, ...) { pa_ioline_puts(l, t); pa_xfree(t); } + +pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l) { + pa_iochannel *r; + + pa_assert(l); + + if (!l->io) + return NULL; + + r = l->io; + l->io = NULL; + + pa_iochannel_set_callback(r, NULL, NULL); + + return r; +} + +pa_bool_t pa_ioline_is_drained(pa_ioline *l) { + pa_assert(l); + + return l->wbuf_valid_length <= 0; +} diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h index 9f32d60f..d973a3c7 100644 --- a/src/pulsecore/ioline.h +++ b/src/pulsecore/ioline.h @@ -32,6 +32,7 @@ typedef struct pa_ioline pa_ioline; typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata); +typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata); pa_ioline* pa_ioline_new(pa_iochannel *io); void pa_ioline_unref(pa_ioline *l); @@ -47,7 +48,17 @@ void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR( /* Set the callback function that is called for every recieved line */ void pa_ioline_set_callback(pa_ioline*io, pa_ioline_cb_t callback, void *userdata); +/* Set the callback function that is called when everything has been written */ +void pa_ioline_set_drain_callback(pa_ioline*io, pa_ioline_drain_cb_t callback, void *userdata); + /* Make sure to close the ioline object as soon as the send buffer is emptied */ void pa_ioline_defer_close(pa_ioline *io); +/* Returns TRUE when everything was written */ +pa_bool_t pa_ioline_is_drained(pa_ioline *io); + +/* Detaches from the iochannel and returns it. Data that has already + * been read will not be available in the detached iochannel */ +pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l); + #endif diff --git a/src/pulsecore/ipacl.h b/src/pulsecore/ipacl.h index 7b7ffa61..a3661397 100644 --- a/src/pulsecore/ipacl.h +++ b/src/pulsecore/ipacl.h @@ -1,5 +1,5 @@ -#ifndef fooparseaddrhfoo -#define fooparseaddrhfoo +#ifndef foopulsecoreipaclhfoo +#define foopulsecoreipaclhfoo /*** This file is part of PulseAudio. diff --git a/src/pulsecore/ltdl-helper.h b/src/pulsecore/ltdl-helper.h index 4c4f018a..9f346969 100644 --- a/src/pulsecore/ltdl-helper.h +++ b/src/pulsecore/ltdl-helper.h @@ -29,4 +29,3 @@ 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 index a5ca6964..cf662510 100644 --- a/src/pulsecore/macro.h +++ b/src/pulsecore/macro.h @@ -29,6 +29,7 @@ #include <unistd.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <pulse/gccmacro.h> @@ -57,18 +58,27 @@ #define PA_PAGE_SIZE ((size_t) 4096) #endif +/* Rounds down */ +static inline void* pa_align_ptr(const void *p) { + return (void*) (((size_t) p) & ~(sizeof(void*)-1)); +} +#define PA_ALIGN_PTR(x) (pa_align_ptr(x)) + +/* Rounds up */ 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)) +/* Rounds down */ 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)) +/* Rounds up */ static inline size_t pa_page_align(size_t l) { - return l & ~(PA_PAGE_SIZE-1); + return ((l + PA_PAGE_SIZE - 1) / PA_PAGE_SIZE) * PA_PAGE_SIZE; } #define PA_PAGE_ALIGN(x) (pa_page_align(x)) @@ -252,6 +262,9 @@ typedef int pa_bool_t; #define PA_DEBUG_TRAP raise(SIGTRAP) #endif +#define pa_memzero(x,l) (memset((x), 0, (l))) +#define pa_zero(x) (pa_memzero(&(x), sizeof(x))) + /* We include this at the very last place */ #include <pulsecore/log.h> diff --git a/src/pulsecore/memtrap.c b/src/pulsecore/memtrap.c index e04e838e..e06f60ca 100644 --- a/src/pulsecore/memtrap.c +++ b/src/pulsecore/memtrap.c @@ -26,12 +26,17 @@ #include <signal.h> #include <sys/mman.h> +/* This is deprecated on glibc but is still used by FreeBSD */ +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +#endif + #include <pulse/xmalloc.h> -#include <pulsecore/semaphore.h> -#include <pulsecore/macro.h> -#include <pulsecore/mutex.h> #include <pulsecore/core-util.h> +#include <pulsecore/aupdate.h> +#include <pulsecore/atomic.h> +#include <pulsecore/once.h> #include "memtrap.h" @@ -43,13 +48,13 @@ struct pa_memtrap { }; static pa_memtrap *memtraps[2] = { NULL, NULL }; -static pa_atomic_t read_lock = PA_ATOMIC_INIT(0); -static pa_static_semaphore semaphore = PA_STATIC_SEMAPHORE_INIT; -static pa_static_mutex write_lock = PA_STATIC_MUTEX_INIT; +static pa_aupdate *aupdate; -#define MSB (1U << (sizeof(unsigned)*8U-1)) -#define WHICH(n) (!!((n) & MSB)) -#define COUNTER(n) ((n) & ~MSB) +static void allocate_aupdate(void) { + PA_ONCE_BEGIN { + aupdate = pa_aupdate_new(); + } PA_ONCE_END; +} pa_bool_t pa_memtrap_is_good(pa_memtrap *m) { pa_assert(m); @@ -62,19 +67,11 @@ static void sigsafe_error(const char *s) { } static void signal_handler(int sig, siginfo_t* si, void *data) { - unsigned n, j; + unsigned j; pa_memtrap *m; void *r; - /* Increase the lock counter */ - n = (unsigned) pa_atomic_inc(&read_lock); - - /* The uppermost bit tells us which list to look at */ - j = WHICH(n); - - /* When n is 0 we have about 2^31 threads running that - * all got a sigbus at the same time, oh my! */ - pa_assert(COUNTER(n)+1 > 0); + j = pa_aupdate_read_begin(aupdate); for (m = memtraps[j]; m; m = m->next[j]) if (si->si_addr >= m->start && @@ -94,33 +91,16 @@ static void signal_handler(int sig, siginfo_t* si, void *data) { pa_assert(r == m->start); - pa_atomic_dec(&read_lock); - - /* Post the semaphore */ - pa_semaphore_post(pa_static_semaphore_get(&semaphore, 0)); - + pa_aupdate_read_end(aupdate); return; fail: + pa_aupdate_read_end(aupdate); + sigsafe_error("Failed to handle SIGBUS.\n"); - pa_atomic_dec(&read_lock); abort(); } -static void memtrap_swap(unsigned n) { - - for (;;) { - - /* If the read counter is > 0 wait; if it is 0 try to swap the lists */ - if (COUNTER(n) > 0) - pa_semaphore_wait(pa_static_semaphore_get(&semaphore, 0)); - else if (pa_atomic_cmpxchg(&read_lock, (int) n, (int) (n ^ MSB))) - break; - - n = (unsigned) pa_atomic_load(&read_lock); - } -} - static void memtrap_link(pa_memtrap *m, unsigned j) { pa_assert(m); @@ -143,92 +123,76 @@ static void memtrap_unlink(pa_memtrap *m, unsigned j) { pa_memtrap* pa_memtrap_add(const void *start, size_t size) { pa_memtrap *m = NULL; - pa_mutex *lock; - unsigned n, j; + unsigned j; pa_assert(start); pa_assert(size > 0); - pa_assert(PA_PAGE_ALIGN_PTR(start) == start); - pa_assert(PA_PAGE_ALIGN(size) == size); - lock = pa_static_mutex_get(&write_lock, FALSE, FALSE); - pa_mutex_lock(lock); - - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); + start = PA_PAGE_ALIGN_PTR(start); + size = PA_PAGE_ALIGN(size); m = pa_xnew(pa_memtrap, 1); m->start = (void*) start; m->size = size; pa_atomic_store(&m->bad, 0); - memtrap_link(m, !j); - memtrap_swap(n); - memtrap_link(m, j); + allocate_aupdate(); - pa_mutex_unlock(lock); + j = pa_aupdate_write_begin(aupdate); + memtrap_link(m, j); + j = pa_aupdate_write_swap(aupdate); + memtrap_link(m, j); + pa_aupdate_write_end(aupdate); return m; } void pa_memtrap_remove(pa_memtrap *m) { - unsigned n, j; - pa_mutex *lock; + unsigned j; pa_assert(m); - lock = pa_static_mutex_get(&write_lock, FALSE, FALSE); - pa_mutex_lock(lock); - - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); + allocate_aupdate(); - memtrap_unlink(m, !j); - memtrap_swap(n); + j = pa_aupdate_write_begin(aupdate); memtrap_unlink(m, j); + j = pa_aupdate_write_swap(aupdate); + memtrap_unlink(m, j); + pa_aupdate_write_end(aupdate); pa_xfree(m); - - pa_mutex_unlock(lock); } pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size) { - unsigned n, j; - pa_mutex *lock; + unsigned j; pa_assert(m); pa_assert(start); pa_assert(size > 0); - pa_assert(PA_PAGE_ALIGN_PTR(start) == start); - pa_assert(PA_PAGE_ALIGN(size) == size); - lock = pa_static_mutex_get(&write_lock, FALSE, FALSE); - pa_mutex_lock(lock); + start = PA_PAGE_ALIGN_PTR(start); + size = PA_PAGE_ALIGN(size); + + allocate_aupdate(); + + j = pa_aupdate_write_begin(aupdate); if (m->start == start && m->size == size) goto unlock; - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); - - memtrap_unlink(m, !j); - memtrap_swap(n); memtrap_unlink(m, j); + j = pa_aupdate_write_swap(aupdate); m->start = (void*) start; m->size = size; pa_atomic_store(&m->bad, 0); - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); - - memtrap_link(m, !j); - memtrap_swap(n); + j = pa_aupdate_write_swap(aupdate); memtrap_link(m, j); unlock: - pa_mutex_unlock(lock); + pa_aupdate_write_end(aupdate); return m; } @@ -236,10 +200,7 @@ unlock: void pa_memtrap_install(void) { struct sigaction sa; - /* Before we install the signal handler, make sure the semaphore - * is valid so that the initialization of the semaphore - * doesn't have to happen from the signal handler */ - pa_static_semaphore_get(&semaphore, 0); + allocate_aupdate(); memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = signal_handler; diff --git a/src/pulsecore/memtrap.h b/src/pulsecore/memtrap.h index f7da7083..fa38da58 100644 --- a/src/pulsecore/memtrap.h +++ b/src/pulsecore/memtrap.h @@ -34,7 +34,7 @@ * still 'good' i.e. no SIGBUS has happened yet for it. * * Intended usage is to handle memory mapped in which is controlled by - * other processes that might execute ftruncate() or when mapping in + * other processes that might execute ftruncate() or when mapping inb * hardware resources that might get invalidated when unplugged. */ typedef struct pa_memtrap pa_memtrap; diff --git a/src/pulsecore/mime-type.c b/src/pulsecore/mime-type.c new file mode 100644 index 00000000..b9fe9444 --- /dev/null +++ b/src/pulsecore/mime-type.c @@ -0,0 +1,182 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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-util.h> + +#include "mime-type.h" + +pa_bool_t pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm) { + + pa_assert(pa_channel_map_compatible(cm, ss)); + + switch (ss->format) { + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_U8: + + if (ss->rate != 8000 && + ss->rate != 11025 && + ss->rate != 16000 && + ss->rate != 22050 && + ss->rate != 24000 && + ss->rate != 32000 && + ss->rate != 44100 && + ss->rate != 48000) + return FALSE; + + if (ss->channels != 1 && + ss->channels != 2) + return FALSE; + + if ((cm->channels == 1 && cm->map[0] != PA_CHANNEL_POSITION_MONO) || + (cm->channels == 2 && (cm->map[0] != PA_CHANNEL_POSITION_LEFT || cm->map[1] != PA_CHANNEL_POSITION_RIGHT))) + return FALSE; + + return TRUE; + + case PA_SAMPLE_ULAW: + + if (ss->rate != 8000) + return FALSE; + + if (ss->channels != 1) + return FALSE; + + if (cm->map[0] != PA_CHANNEL_POSITION_MONO) + return FALSE; + + return TRUE; + + default: + return FALSE; + } +} + +void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm) { + + pa_assert(pa_channel_map_compatible(cm, ss)); + + /* Turns the sample type passed in into the next 'better' one that + * can be encoded for HTTP. If there is no 'better' one we pick + * the 'best' one that is 'worse'. */ + + if (ss->channels > 2) + ss->channels = 2; + + if (ss->rate > 44100) + ss->rate = 48000; + else if (ss->rate > 32000) + ss->rate = 44100; + else if (ss->rate > 24000) + ss->rate = 32000; + else if (ss->rate > 22050) + ss->rate = 24000; + else if (ss->rate > 16000) + ss->rate = 22050; + else if (ss->rate > 11025) + ss->rate = 16000; + else if (ss->rate > 8000) + ss->rate = 11025; + else + ss->rate = 8000; + + switch (ss->format) { + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + ss->format = PA_SAMPLE_S24BE; + break; + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S16LE: + ss->format = PA_SAMPLE_S16BE; + break; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + + if (ss->rate == 8000 && ss->channels == 1) + ss->format = PA_SAMPLE_ULAW; + else + ss->format = PA_SAMPLE_S16BE; + break; + + case PA_SAMPLE_U8: + ss->format = PA_SAMPLE_U8; + break; + + case PA_SAMPLE_MAX: + case PA_SAMPLE_INVALID: + pa_assert_not_reached(); + } + + pa_channel_map_init_auto(cm, ss->channels, PA_CHANNEL_MAP_DEFAULT); + + pa_assert(pa_sample_spec_is_mime(ss, cm)); +} + +char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) { + pa_assert(pa_channel_map_compatible(cm, ss)); + + if (!pa_sample_spec_is_mime(ss, cm)) + return NULL; + + switch (ss->format) { + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_U8: + /* Stupid UPnP implementations (PS3...) choke on spaces in + * the mime type, that's why we write only ';' here, + * instead of '; '. */ + return pa_sprintf_malloc("audio/%s;rate=%u;channels=%u", + ss->format == PA_SAMPLE_S16BE ? "L16" : + (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"), + ss->rate, ss->channels); + + case PA_SAMPLE_ULAW: + return pa_xstrdup("audio/basic"); + + default: + pa_assert_not_reached(); + } + + pa_assert(pa_sample_spec_valid(ss)); +} + +char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm) { + pa_sample_spec ss = *_ss; + pa_channel_map cm = *_cm; + + pa_sample_spec_mimefy(&ss, &cm); + + return pa_sample_spec_to_mime_type(&ss, &cm); +} diff --git a/src/pulsecore/mime-type.h b/src/pulsecore/mime-type.h new file mode 100644 index 00000000..db77379b --- /dev/null +++ b/src/pulsecore/mime-type.h @@ -0,0 +1,37 @@ +#ifndef foopulsecoremimetypehfoo +#define foopulsecoremimetypehfoo +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to 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 <pulse/sample.h> +#include <pulse/channelmap.h> + +pa_bool_t pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm); +void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm); +char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm); +char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm); + +#endif diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c index 73c67a8b..c7d734d9 100644 --- a/src/pulsecore/modargs.c +++ b/src/pulsecore/modargs.c @@ -84,8 +84,11 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { KEY, VALUE_START, VALUE_SIMPLE, + VALUE_SIMPLE_ESCAPED, VALUE_DOUBLE_QUOTES, - VALUE_TICKS + VALUE_DOUBLE_QUOTES_ESCAPED, + VALUE_TICKS, + VALUE_TICKS_ESCAPED } state; const char *p, *key = NULL, *value = NULL; @@ -131,9 +134,16 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { value = p+1; value_len = 0; } else if (isspace(*p)) { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_xstrdup(""), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_SIMPLE_ESCAPED; + value = p; + value_len = 1; } else { state = VALUE_SIMPLE; value = p; @@ -143,30 +153,63 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { case VALUE_SIMPLE: if (isspace(*p)) { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_SIMPLE_ESCAPED; + value_len++; } else value_len++; break; + case VALUE_SIMPLE_ESCAPED: + state = VALUE_SIMPLE; + value_len++; + break; + case VALUE_DOUBLE_QUOTES: if (*p == '"') { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_DOUBLE_QUOTES_ESCAPED; + value_len++; } else value_len++; break; + case VALUE_DOUBLE_QUOTES_ESCAPED: + state = VALUE_DOUBLE_QUOTES; + value_len++; + break; + case VALUE_TICKS: if (*p == '\'') { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_TICKS_ESCAPED; + value_len++; } else value_len++; break; + + case VALUE_TICKS_ESCAPED: + state = VALUE_TICKS; + value_len++; + break; } } @@ -352,3 +395,23 @@ int pa_modargs_get_sample_spec_and_channel_map( return 0; } + +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m) { + const char *v; + pa_proplist *n; + + pa_assert(ma); + pa_assert(name); + pa_assert(p); + + if (!(v = pa_modargs_get_value(ma, name, NULL))) + return 0; + + if (!(n = pa_proplist_from_string(v))) + return -1; + + pa_proplist_update(p, m, n); + pa_proplist_free(n); + + return 0; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h index 809fb27e..b3125b10 100644 --- a/src/pulsecore/modargs.h +++ b/src/pulsecore/modargs.h @@ -58,4 +58,6 @@ structure if no channel_map is found, using pa_channel_map_init_auto() */ int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *ss, pa_channel_map *map, pa_channel_map_def_t def); +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m); + #endif diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c index 00fb9c43..b5ee9f56 100644 --- a/src/pulsecore/modinfo.c +++ b/src/pulsecore/modinfo.c @@ -38,6 +38,7 @@ #define PA_SYMBOL_DESCRIPTION "pa__get_description" #define PA_SYMBOL_USAGE "pa__get_usage" #define PA_SYMBOL_VERSION "pa__get_version" +#define PA_SYMBOL_DEPRECATED "pa__get_deprecated" #define PA_SYMBOL_LOAD_ONCE "pa__load_once" pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) { @@ -61,6 +62,9 @@ pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) { if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_VERSION))) i->version = pa_xstrdup(func()); + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DEPRECATED))) + i->deprecated = pa_xstrdup(func()); + if ((func2 = (pa_bool_t (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_LOAD_ONCE))) i->load_once = func2(); @@ -91,5 +95,6 @@ void pa_modinfo_free(pa_modinfo *i) { pa_xfree(i->description); pa_xfree(i->usage); pa_xfree(i->version); + pa_xfree(i->deprecated); pa_xfree(i); } diff --git a/src/pulsecore/modinfo.h b/src/pulsecore/modinfo.h index 407e602a..baad0de7 100644 --- a/src/pulsecore/modinfo.h +++ b/src/pulsecore/modinfo.h @@ -30,6 +30,7 @@ typedef struct pa_modinfo { char *description; char *usage; char *version; + char *deprecated; pa_bool_t load_once; } pa_modinfo; diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c index 42fd912c..5bcdd898 100644 --- a/src/pulsecore/module.c +++ b/src/pulsecore/module.c @@ -48,10 +48,12 @@ #define PA_SYMBOL_DONE "pa__done" #define PA_SYMBOL_LOAD_ONCE "pa__load_once" #define PA_SYMBOL_GET_N_USED "pa__get_n_used" +#define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated" pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { pa_module *m = NULL; pa_bool_t (*load_once)(void); + const char* (*get_deprecated)(void); pa_modinfo *mi; pa_assert(c); @@ -89,6 +91,13 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { } } + if ((get_deprecated = (const char* (*) (void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_DEPRECATE))) { + const char *t; + + if ((t = get_deprecated())) + pa_log_warn("%s is deprecated: %s", name, t); + } + if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) { pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name); goto fail; diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h index 3f697348..af89d793 100644 --- a/src/pulsecore/module.h +++ b/src/pulsecore/module.h @@ -78,6 +78,10 @@ int pa_module_get_n_used(pa_module*m); const char * pa__get_version(void) { return s; } \ struct __stupid_useless_struct_to_allow_trailing_semicolon +#define PA_MODULE_DEPRECATED(s) \ + const char * pa__get_deprecated(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + #define PA_MODULE_LOAD_ONCE(b) \ pa_bool_t pa__load_once(void) { return b; } \ struct __stupid_useless_struct_to_allow_trailing_semicolon diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c index 5b531220..44cd9a05 100644 --- a/src/pulsecore/parseaddr.c +++ b/src/pulsecore/parseaddr.c @@ -25,6 +25,8 @@ #include <string.h> #include <stdlib.h> +#include <arpa/inet.h> +#include <sys/socket.h> #include <pulse/xmalloc.h> #include <pulse/util.h> @@ -131,3 +133,17 @@ int pa_parse_address(const char *name, pa_parsed_address *ret_p) { return 0; } + +pa_bool_t pa_is_ip_address(const char *a) { + char buf[INET6_ADDRSTRLEN]; + + pa_assert(a); + + if (inet_pton(AF_INET6, a, buf) >= 1) + return TRUE; + + if (inet_pton(AF_INET, a, buf) >= 1) + return TRUE; + + return FALSE; +} diff --git a/src/pulsecore/parseaddr.h b/src/pulsecore/parseaddr.h index 5fbcb9a7..a1071b0a 100644 --- a/src/pulsecore/parseaddr.h +++ b/src/pulsecore/parseaddr.h @@ -1,5 +1,5 @@ -#ifndef fooparseaddrhfoo -#define fooparseaddrhfoo +#ifndef foopulsecoreparseaddrhfoo +#define foopulsecoreparseaddrhfoo /*** This file is part of PulseAudio. @@ -24,6 +24,8 @@ #include <inttypes.h> +#include <pulsecore/macro.h> + typedef enum pa_parsed_address_type { PA_PARSED_ADDRESS_UNIX, PA_PARSED_ADDRESS_TCP4, @@ -39,4 +41,6 @@ typedef struct pa_parsed_address { int pa_parse_address(const char *a, pa_parsed_address *ret_p); +pa_bool_t pa_is_ip_address(const char *a); + #endif diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c index eac8927c..d9769bc7 100644 --- a/src/pulsecore/proplist-util.c +++ b/src/pulsecore/proplist-util.c @@ -168,20 +168,20 @@ void pa_init_proplist(pa_proplist *p) { } 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); + char *u; + + if ((u = pa_get_user_name_malloc())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_USER, u); + pa_xfree(u); } } if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_HOST)) { - char 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); + char *h; + + if ((h = pa_get_host_name_malloc())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, h); + pa_xfree(h); } } diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index 7e7126ea..ad7cd045 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -947,10 +947,10 @@ static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const connection_write(c, &ok, sizeof(int32_t)); if (request == ESD_PROTO_STANDBY) - ok = pa_sink_suspend_all(c->protocol->core, TRUE) >= 0; + ok = pa_sink_suspend_all(c->protocol->core, TRUE, PA_SUSPEND_USER) >= 0; else { pa_assert(request == ESD_PROTO_RESUME); - ok = pa_sink_suspend_all(c->protocol->core, FALSE) >= 0; + ok = pa_sink_suspend_all(c->protocol->core, FALSE, PA_SUSPEND_USER) >= 0; } connection_write(c, &ok, sizeof(int32_t)); diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index f3b93819..5220cc91 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2005-2006 Lennart Poettering + Copyright 2005-2009 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -26,36 +26,68 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <errno.h> #include <pulse/util.h> #include <pulse/xmalloc.h> +#include <pulse/timeval.h> #include <pulsecore/ioline.h> +#include <pulsecore/thread-mq.h> #include <pulsecore/macro.h> #include <pulsecore/log.h> #include <pulsecore/namereg.h> #include <pulsecore/cli-text.h> #include <pulsecore/shared.h> +#include <pulsecore/core-error.h> +#include <pulsecore/mime-type.h> #include "protocol-http.h" /* Don't allow more than this many concurrent connections */ #define MAX_CONNECTIONS 10 -#define internal_server_error(c) http_message((c), 500, "Internal Server Error", NULL) - #define URL_ROOT "/" #define URL_CSS "/style" #define URL_STATUS "/status" +#define URL_LISTEN "/listen" +#define URL_LISTEN_SOURCE "/listen/source/" + +#define MIME_HTML "text/html; charset=utf-8" +#define MIME_TEXT "text/plain; charset=utf-8" +#define MIME_CSS "text/css" + +#define HTML_HEADER(t) \ + "<?xml version=\"1.0\"?>\n" \ + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \ + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" \ + " <head>\n" \ + " <title>"t"</title>\n" \ + " <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \ + " </head>\n" \ + " <body>\n" + +#define HTML_FOOTER \ + " </body>\n" \ + "</html>\n" + +#define RECORD_BUFFER_SECONDS (5) +#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC) + +enum state { + STATE_REQUEST_LINE, + STATE_MIME_HEADER, + STATE_DATA +}; struct connection { pa_http_protocol *protocol; + pa_iochannel *io; pa_ioline *line; - enum { - REQUEST_LINE, - MIME_HEADER, - DATA - } state; + pa_memblockq *output_memblockq; + pa_source_output *source_output; + pa_client *client; + enum state state; char *url; pa_module *module; }; @@ -65,58 +97,500 @@ struct pa_http_protocol { pa_core *core; pa_idxset *connections; + + pa_strlist *servers; }; -static void http_response(struct connection *c, int code, const char *msg, const char *mime) { - char s[256]; +enum { + SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +/* Called from main context */ +static void connection_unlink(struct connection *c) { + pa_assert(c); + + if (c->source_output) { + pa_source_output_unlink(c->source_output); + c->source_output->userdata = NULL; + pa_source_output_unref(c->source_output); + } + + if (c->client) + pa_client_free(c->client); + + pa_xfree(c->url); + + if (c->line) + pa_ioline_unref(c->line); + + if (c->io) + pa_iochannel_free(c->io); + + if (c->output_memblockq) + pa_memblockq_free(c->output_memblockq); + + pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + + pa_xfree(c); +} + +/* Called from main context */ +static int do_write(struct connection *c) { + pa_memchunk chunk; + ssize_t r; + void *p; + + pa_assert(c); + + if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) + return 0; + + pa_assert(chunk.memblock); + pa_assert(chunk.length > 0); + + p = pa_memblock_acquire(chunk.memblock); + r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); + pa_memblock_release(chunk.memblock); + + pa_memblock_unref(chunk.memblock); + + if (r < 0) { + + if (errno == EINTR || errno == EAGAIN) + return 0; + + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_memblockq_drop(c->output_memblockq, (size_t) r); + + return 0; +} + +/* Called from main context */ +static void do_work(struct connection *c) { + pa_assert(c); + + if (pa_iochannel_is_hungup(c->io)) + goto fail; + + if (pa_iochannel_is_writable(c->io)) + if (do_write(c) < 0) + goto fail; + + return; + +fail: + connection_unlink(c); +} + +/* Called from thread context, except when it is not */ +static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_source_output *o = PA_SOURCE_OUTPUT(m); + struct connection *c; + + pa_source_output_assert_ref(o); + + if (!(c = o->userdata)) + return -1; + + switch (code) { + + case SOURCE_OUTPUT_MESSAGE_POST_DATA: + /* While this function is usually called from IO thread + * context, this specific command is not! */ + pa_memblockq_push_align(c->output_memblockq, chunk); + do_work(c); + break; + + default: + return pa_source_output_process_msg(m, code, userdata, offset, chunk); + } + + return 0; +} + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + struct connection *c; + + pa_source_output_assert_ref(o); + pa_assert_se(c = o->userdata); + pa_assert(chunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +/* Called from main context */ +static void source_output_kill_cb(pa_source_output *o) { + struct connection*c; + + pa_source_output_assert_ref(o); + pa_assert_se(c = o->userdata); + + connection_unlink(c); +} + +/* Called from main context */ +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + struct connection*c; + + pa_source_output_assert_ref(o); + pa_assert_se(c = o->userdata); + + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); +} + +/*** client callbacks ***/ +static void client_kill_cb(pa_client *client) { + struct connection*c; + + pa_assert(client); + pa_assert_se(c = client->userdata); + + connection_unlink(c); +} + +/*** pa_iochannel callbacks ***/ +static void io_callback(pa_iochannel*io, void *userdata) { + struct connection *c = userdata; + + pa_assert(c); + pa_assert(io); + + do_work(c); +} + +static char *escape_html(const char *t) { + pa_strbuf *sb; + const char *p, *e; + + sb = pa_strbuf_new(); + + for (e = p = t; *p; p++) { + + if (*p == '>' || *p == '<' || *p == '&') { + + if (p > e) { + pa_strbuf_putsn(sb, e, p-e); + e = p + 1; + } + + if (*p == '>') + pa_strbuf_puts(sb, ">"); + else if (*p == '<') + pa_strbuf_puts(sb, "<"); + else + pa_strbuf_puts(sb, "&"); + } + } + + if (p > e) + pa_strbuf_putsn(sb, e, p-e); + + return pa_strbuf_tostring_free(sb); +} + +static void http_response( + struct connection *c, + int code, + const char *msg, + const char *mime) { + + char *s; pa_assert(c); pa_assert(msg); pa_assert(mime); - pa_snprintf(s, sizeof(s), - "HTTP/1.0 %i %s\n" - "Connection: close\n" - "Content-Type: %s\n" - "Cache-Control: no-cache\n" - "Expires: 0\n" - "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" - "\n", code, msg, mime); - + s = pa_sprintf_malloc( + "HTTP/1.0 %i %s\n" + "Connection: close\n" + "Content-Type: %s\n" + "Cache-Control: no-cache\n" + "Expires: 0\n" + "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" + "\n", code, msg, mime); pa_ioline_puts(c->line, s); + pa_xfree(s); } -static void http_message(struct connection *c, int code, const char *msg, const char *text) { - char s[256]; +static void html_response( + struct connection *c, + int code, + const char *msg, + const char *text) { + + char *s; pa_assert(c); - http_response(c, code, msg, "text/html"); + http_response(c, code, msg, MIME_HTML); if (!text) text = msg; - pa_snprintf(s, sizeof(s), - "<?xml version=\"1.0\"?>\n" - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" - "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>%s</title></head>\n" - "<body>%s</body></html>\n", - text, text); + s = pa_sprintf_malloc( + HTML_HEADER("%s") + "%s" + HTML_FOOTER, + text, text); pa_ioline_puts(c->line, s); + pa_xfree(s); + pa_ioline_defer_close(c->line); } +static void html_print_field(pa_ioline *line, const char *left, const char *right) { + char *eleft, *eright; + + eleft = escape_html(left); + eright = escape_html(right); + + pa_ioline_printf(line, + "<tr><td><b>%s</b></td>" + "<td>%s</td></tr>\n", eleft, eright); + + pa_xfree(eleft); + pa_xfree(eright); +} + +static void handle_root(struct connection *c) { + char *t; -static void connection_unlink(struct connection *c) { pa_assert(c); - if (c->url) - pa_xfree(c->url); + http_response(c, 200, "OK", MIME_HTML); - pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + pa_ioline_puts(c->line, + HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION) + "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" + "<table>\n"); + + t = pa_get_user_name_malloc(); + html_print_field(c->line, "User Name:", t); + pa_xfree(t); + + t = pa_get_host_name_malloc(); + html_print_field(c->line, "Host name:", t); + pa_xfree(t); + + t = pa_machine_id(); + html_print_field(c->line, "Machine ID:", t); + pa_xfree(t); + + t = pa_uname_string(); + html_print_field(c->line, "System:", t); + pa_xfree(t); + + t = pa_sprintf_malloc("%lu", (unsigned long) getpid()); + html_print_field(c->line, "Process ID:", t); + pa_xfree(t); + + pa_ioline_puts(c->line, + "</table>\n" + "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n" + "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n" + HTML_FOOTER); + + pa_ioline_defer_close(c->line); +} + +static void handle_css(struct connection *c) { + pa_assert(c); + + http_response(c, 200, "OK", MIME_CSS); + + pa_ioline_puts(c->line, + "body { color: black; background-color: white; }\n" + "a:link, a:visited { color: #900000; }\n" + "div.news-date { font-size: 80%; font-style: italic; }\n" + "pre { background-color: #f0f0f0; padding: 0.4cm; }\n" + ".grey { color: #8f8f8f; font-size: 80%; }" + "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" + "td { padding-left:10px; padding-right:10px; }\n"); + + pa_ioline_defer_close(c->line); +} + +static void handle_status(struct connection *c) { + char *r; + + pa_assert(c); + + http_response(c, 200, "OK", MIME_TEXT); + r = pa_full_status_string(c->protocol->core); + pa_ioline_puts(c->line, r); + pa_xfree(r); + + pa_ioline_defer_close(c->line); +} + +static void handle_listen(struct connection *c) { + pa_source *source; + pa_sink *sink; + uint32_t idx; + + http_response(c, 200, "OK", MIME_HTML); + + pa_ioline_puts(c->line, + HTML_HEADER("Listen") + "<h2>Sinks</h2>\n" + "<p>\n"); + + PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) { + char *t, *m; + + t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + + pa_ioline_printf(c->line, + "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n", + sink->monitor_source->name, m, t); + + pa_xfree(t); + pa_xfree(m); + } + + pa_ioline_puts(c->line, + "</p>\n" + "<h2>Sources</h2>\n" + "<p>\n"); + + PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) { + char *t, *m; + + if (source->monitor_of) + continue; + + t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + + pa_ioline_printf(c->line, + "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n", + source->name, m, t); + + pa_xfree(m); + pa_xfree(t); + + } + + pa_ioline_puts(c->line, + "</p>\n" + HTML_FOOTER); + + pa_ioline_defer_close(c->line); +} + +static void line_drain_callback(pa_ioline *l, void *userdata) { + struct connection *c; + + pa_assert(l); + pa_assert_se(c = userdata); + + /* We don't need the line reader anymore, instead we need a real + * binary io channel */ + pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line)); + pa_iochannel_set_callback(c->io, io_callback, c); + + pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq)); pa_ioline_unref(c->line); - pa_xfree(c); + c->line = NULL; +} + +static void handle_listen_prefix(struct connection *c, const char *source_name) { + pa_source *source; + pa_source_output_new_data data; + pa_sample_spec ss; + pa_channel_map cm; + char *t; + size_t l; + + pa_assert(c); + pa_assert(source_name); + + pa_assert(c->line); + pa_assert(!c->io); + + if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) { + html_response(c, 404, "Source not found", NULL); + return; + } + + ss = source->sample_spec; + cm = source->channel_map; + + pa_sample_spec_mimefy(&ss, &cm); + + pa_source_output_new_data_init(&data); + data.driver = __FILE__; + data.module = c->module; + data.client = c->client; + data.source = source; + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_source_output_new_data_set_sample_spec(&data, &ss); + pa_source_output_new_data_set_channel_map(&data, &cm); + + pa_source_output_new(&c->source_output, c->protocol->core, &data, 0); + pa_source_output_new_data_done(&data); + + if (!c->source_output) { + html_response(c, 403, "Cannot create source output", NULL); + return; + } + + c->source_output->parent.process_msg = source_output_process_msg; + c->source_output->push = source_output_push_cb; + c->source_output->kill = source_output_kill_cb; + c->source_output->get_latency = source_output_get_latency_cb; + c->source_output->userdata = c; + + pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + + l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); + c->output_memblockq = pa_memblockq_new( + 0, + l, + 0, + pa_frame_size(&ss), + 1, + 0, + 0, + NULL); + + pa_source_output_put(c->source_output); + + t = pa_sample_spec_to_mime_type(&ss, &cm); + http_response(c, 200, "OK", t); + pa_xfree(t); + + pa_ioline_set_callback(c->line, NULL, NULL); + + if (pa_ioline_is_drained(c->line)) + line_drain_callback(c->line, c); + else + pa_ioline_set_drain_callback(c->line, line_drain_callback, c); +} + +static void handle_url(struct connection *c) { + pa_assert(c); + + pa_log_debug("Request for %s", c->url); + + if (pa_streq(c->url, URL_ROOT)) + handle_root(c); + else if (pa_streq(c->url, URL_CSS)) + handle_css(c); + else if (pa_streq(c->url, URL_STATUS)) + handle_status(c); + else if (pa_streq(c->url, URL_LISTEN)) + handle_listen(c); + else if (pa_startswith(c->url, URL_LISTEN_SOURCE)) + handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1); + else + html_response(c, 404, "Not Found", NULL); } static void line_callback(pa_ioline *line, const char *s, void *userdata) { @@ -131,93 +605,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } switch (c->state) { - case REQUEST_LINE: { - if (memcmp(s, "GET ", 4)) + case STATE_REQUEST_LINE: { + if (!pa_startswith(s, "GET ")) goto fail; s +=4; c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?")); - c->state = MIME_HEADER; + c->state = STATE_MIME_HEADER; break; - } - case MIME_HEADER: { + case STATE_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]; - pa_sink *def_sink; - pa_source *def_source; - http_response(c, 200, "OK", "text/html"); - - pa_ioline_puts(c->line, - "<?xml version=\"1.0\"?>\n" - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" - "<html xmlns=\"http://www.w3.org/1999/xhtml\"><title>"PACKAGE_NAME" "PACKAGE_VERSION"</title>\n" - "<link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/></head><body>\n"); - - pa_ioline_puts(c->line, - "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" - "<table>"); - -#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "<tr><td><b>%s</b></td><td>%s</td></tr>\n",(a),(b)) - - PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt))); - PRINTF_FIELD("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)); - - def_sink = pa_namereg_get_default_sink(c->protocol->core); - def_source = pa_namereg_get_default_source(c->protocol->core); - - PRINTF_FIELD("Default Sink:", def_sink ? def_sink->name : "n/a"); - PRINTF_FIELD("Default Source:", def_source ? def_source->name : "n/a"); - - pa_ioline_puts(c->line, "</table>"); - - pa_ioline_puts(c->line, "<p><a href=\"/status\">Click here</a> for an extensive server status report.</p>"); - - pa_ioline_puts(c->line, "</body></html>\n"); - - pa_ioline_defer_close(c->line); - } else if (!strcmp(c->url, URL_CSS)) { - http_response(c, 200, "OK", "text/css"); - - pa_ioline_puts(c->line, - "body { color: black; background-color: white; margin: 0.5cm; }\n" - "a:link, a:visited { color: #900000; }\n" - "p { margin-left: 0.5cm; margin-right: 0.5cm; }\n" - "h1 { color: #00009F; }\n" - "h2 { color: #00009F; }\n" - "ul { margin-left: .5cm; }\n" - "ol { margin-left: .5cm; }\n" - "pre { margin-left: .5cm; background-color: #f0f0f0; padding: 0.4cm;}\n" - ".grey { color: #afafaf; }\n" - "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" - "td { padding-left:10px; padding-right:10px; }\n"); - - pa_ioline_defer_close(c->line); - } else if (!strcmp(c->url, URL_STATUS)) { - char *r; - - http_response(c, 200, "OK", "text/plain"); - r = pa_full_status_string(c->protocol->core); - pa_ioline_puts(c->line, r); - pa_xfree(r); - - pa_ioline_defer_close(c->line); - } else - http_message(c, 404, "Not Found", NULL); + c->state = STATE_DATA; + handle_url(c); break; } @@ -228,11 +636,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { return; fail: - internal_server_error(c); + html_response(c, 500, "Internal Server Error", NULL); } void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) { struct connection *c; + pa_client_new_data client_data; + char pname[128]; pa_assert(p); pa_assert(io); @@ -244,26 +654,46 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module * return; } - c = pa_xnew(struct connection, 1); + c = pa_xnew0(struct connection, 1); c->protocol = p; - c->line = pa_ioline_new(io); - c->state = REQUEST_LINE; - c->url = NULL; + c->state = STATE_REQUEST_LINE; c->module = m; + c->line = pa_ioline_new(io); pa_ioline_set_callback(c->line, line_callback, c); + pa_client_new_data_init(&client_data); + client_data.module = c->module; + client_data.driver = __FILE__; + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname); + pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname); + c->client = pa_client_new(p->core, &client_data); + pa_client_new_data_done(&client_data); + + if (!c->client) + goto fail; + + c->client->kill = client_kill_cb; + c->client->userdata = c; + pa_idxset_put(p->connections, c, NULL); + + return; + +fail: + if (c) + connection_unlink(c); } void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) { struct connection *c; - void *state = NULL; + uint32_t idx; pa_assert(p); pa_assert(m); - while ((c = pa_idxset_iterate(p->connections, &state, NULL))) + PA_IDXSET_FOREACH(c, p->connections, idx) if (c->module == m) connection_unlink(c); } @@ -273,7 +703,7 @@ static pa_http_protocol* http_protocol_new(pa_core *c) { pa_assert(c); - p = pa_xnew(pa_http_protocol, 1); + p = pa_xnew0(pa_http_protocol, 1); PA_REFCNT_INIT(p); p->core = c; p->connections = pa_idxset_new(NULL, NULL); @@ -315,7 +745,32 @@ void pa_http_protocol_unref(pa_http_protocol *p) { pa_idxset_free(p->connections, NULL, NULL); + pa_strlist_free(p->servers); + pa_assert_se(pa_shared_remove(p->core, "http-protocol") >= 0); pa_xfree(p); } + +void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(name); + + p->servers = pa_strlist_prepend(p->servers, name); +} + +void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(name); + + p->servers = pa_strlist_remove(p->servers, name); +} + +pa_strlist *pa_http_protocol_servers(pa_http_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + return p->servers; +} diff --git a/src/pulsecore/protocol-http.h b/src/pulsecore/protocol-http.h index 40b3d82c..f7517e81 100644 --- a/src/pulsecore/protocol-http.h +++ b/src/pulsecore/protocol-http.h @@ -26,7 +26,7 @@ #include <pulsecore/module.h> #include <pulsecore/modargs.h> #include <pulsecore/iochannel.h> - +#include <pulsecore/strlist.h> typedef struct pa_http_protocol pa_http_protocol; @@ -36,4 +36,8 @@ void pa_http_protocol_unref(pa_http_protocol *p); void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m); void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m); +void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name); +void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name); +pa_strlist *pa_http_protocol_servers(pa_http_protocol *p); + #endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index aecaf71c..e9e2d601 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -3182,10 +3182,10 @@ static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t t static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); pa_tagstruct *reply; - char txt[256]; pa_sink *def_sink; pa_source *def_source; pa_sample_spec fixed_ss; + char *h, *u; pa_native_connection_assert_ref(c); pa_assert(t); @@ -3200,8 +3200,14 @@ static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t 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_host_name(txt, sizeof(txt))); + + u = pa_get_user_name_malloc(); + pa_tagstruct_puts(reply, u); + pa_xfree(u); + + h = pa_get_host_name_malloc(); + pa_tagstruct_puts(reply, h); + pa_xfree(h); fixup_sample_spec(c, &fixed_ss, &c->protocol->core->default_sample_spec); pa_tagstruct_put_sample_spec(reply, &fixed_ss); @@ -4092,7 +4098,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa pa_log_debug("%s all sinks", b ? "Suspending" : "Resuming"); - if (pa_sink_suspend_all(c->protocol->core, b) < 0) { + if (pa_sink_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -4106,7 +4112,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); - if (pa_sink_suspend(sink, b) < 0) { + if (pa_sink_suspend(sink, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -4119,7 +4125,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa pa_log_debug("%s all sources", b ? "Suspending" : "Resuming"); - if (pa_source_suspend_all(c->protocol->core, b) < 0) { + if (pa_source_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -4134,7 +4140,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); - if (pa_source_suspend(source, b) < 0) { + if (pa_source_suspend(source, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index 44fe5973..776d74b6 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -130,7 +130,7 @@ static void connection_unlink(connection *c) { c->io = NULL; } - pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); + pa_idxset_remove_by_data(c->protocol->connections, c, NULL); c->protocol = NULL; connection_unref(c); } diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c index 3a9b384d..dda38834 100644 --- a/src/pulsecore/sample-util.c +++ b/src/pulsecore/sample-util.c @@ -831,9 +831,9 @@ void pa_volume_memchunk( calc_linear_integer_volume(linear, volume); - e = (uint8_t*) ptr + c->length/3; + e = (uint8_t*) ptr + c->length; - for (channel = 0, d = ptr; d < e; d++) { + for (channel = 0, d = ptr; d < e; d += 3) { int64_t t; t = (int64_t)((int32_t) (PA_READ24NE(d) << 8)); @@ -854,9 +854,9 @@ void pa_volume_memchunk( calc_linear_integer_volume(linear, volume); - e = (uint8_t*) ptr + c->length/3; + e = (uint8_t*) ptr + c->length; - for (channel = 0, d = ptr; d < e; d++) { + for (channel = 0, d = ptr; d < e; d += 3) { int64_t t; t = (int64_t)((int32_t) (PA_READ24RE(d) << 8)); @@ -1181,6 +1181,8 @@ pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, case PA_SAMPLE_S32BE: case PA_SAMPLE_S24LE: case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32RE: case PA_SAMPLE_FLOAT32LE: case PA_SAMPLE_FLOAT32BE: cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0); @@ -1189,6 +1191,8 @@ pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S24LE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S24BE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S24_32LE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S24_32BE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b); break; diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c index 307ce7b7..43b8cb3e 100644 --- a/src/pulsecore/sconv-s16le.c +++ b/src/pulsecore/sconv-s16le.c @@ -370,7 +370,7 @@ void pa_sconv_s24_32le_to_s16ne(unsigned n, const uint32_t *a, int16_t *b) { pa_assert(b); for (; n > 0; n--) { - *b = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8) >> 16); + *b = (int16_t) (((int32_t) (UINT32_FROM(*a) << 8)) >> 16); a++; b++; } @@ -416,8 +416,8 @@ void pa_sconv_s24_32le_to_float32ne(unsigned n, const uint32_t *a, float *b) { pa_assert(b); for (; n > 0; n--) { - int32_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8)); - *b = ((float) s) / 0x7FFFFFFF; + int32_t s = (int32_t) (UINT32_FROM(*a) << 8); + *b = (float) s / (float) 0x7FFFFFFF; a ++; b ++; } @@ -428,8 +428,8 @@ void pa_sconv_s24_32le_to_float32re(unsigned n, const uint32_t *a, float *b) { pa_assert(b); for (; n > 0; n--) { - int32_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8)); - float k = ((float) s) / 0x7FFFFFFF; + int32_t s = (int32_t) (UINT32_FROM(*a) << 8); + float k = (float) s / (float) 0x7FFFFFFF; *b = PA_FLOAT32_SWAP(k); a ++; b ++; diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c index 29a9a453..d89f4283 100644 --- a/src/pulsecore/sconv.c +++ b/src/pulsecore/sconv.c @@ -75,7 +75,7 @@ static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { pa_assert(b); for (; n > 0; n--, a++, b++) - *b = (uint8_t) (*a / 0x100 + 0x80); + *b = (uint8_t) ((uint16_t) *a >> 8) + (uint8_t) 0x80U; } /* float32 */ diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index b8c5f786..fab2b3b6 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -69,7 +69,10 @@ #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 */ + * optional, to not require a reboot when upgrading, though. Note that + * on multiarch systems 32bit and 64bit processes might access this + * region simultaneously. The header fields need to be independant + * from the process' word with */ struct shm_marker { pa_atomic_t marker; /* 0xbeefcafe */ pa_atomic_t pid; @@ -79,6 +82,8 @@ struct shm_marker { uint64_t _reserved4; } PA_GCC_PACKED; +#define SHM_MARKER_SIZE PA_ALIGN(sizeof(struct shm_marker)) + static char *segment_name(char *fn, size_t l, unsigned id) { pa_snprintf(fn, l, "/pulse-shm-%u", id); return fn; @@ -97,8 +102,8 @@ int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) { * ones */ pa_shm_cleanup(); - /* Round up to make it aligned */ - size = PA_ALIGN(size); + /* Round up to make it page aligned */ + size = PA_PAGE_ALIGN(size); if (!shared) { m->id = 0; @@ -136,25 +141,25 @@ int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) { goto fail; } - m->size = size + PA_ALIGN(sizeof(struct shm_marker)); + m->size = size + SHM_MARKER_SIZE; if (ftruncate(fd, (off_t) 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, (off_t) 0)) == MAP_FAILED) { + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { pa_log("mmap() failed: %s", pa_cstrerror(errno)); goto fail; } /* We store our PID at the end of the shm block, so that we * can check for dead shm segments later */ - marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - PA_ALIGN(sizeof(struct shm_marker))); + marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - SHM_MARKER_SIZE); pa_atomic_store(&marker->pid, (int) getpid()); pa_atomic_store(&marker->marker, SHM_MARKER); - pa_assert_se(close(fd) == 0); + pa_assert_se(pa_close(fd) == 0); m->do_unlink = TRUE; #else return -1; @@ -197,7 +202,7 @@ void pa_shm_free(pa_shm *m) { #endif } else { #ifdef HAVE_SHM_OPEN - if (munmap(m->ptr, m->size) < 0) + if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0) pa_log("munmap() failed: %s", pa_cstrerror(errno)); if (m->do_unlink) { @@ -214,12 +219,12 @@ void pa_shm_free(pa_shm *m) { #endif } - memset(m, 0, sizeof(*m)); + pa_zero(*m); } void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { void *ptr; - size_t o, ps; + size_t o; pa_assert(m); pa_assert(m->ptr); @@ -233,16 +238,19 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { /* You're welcome to implement this as NOOP on systems that don't * support it */ - /* Align this to multiples of the page size */ + /* Align the pointer up to multiples of the page size */ ptr = (uint8_t*) m->ptr + offset; o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr)); if (o > 0) { - ps = PA_PAGE_SIZE; - ptr = (uint8_t*) ptr + (ps - o); - size -= ps - o; + size_t delta = PA_PAGE_SIZE - o; + ptr = (uint8_t*) ptr + delta; + size -= delta; } + /* Align the size down to multiples of page size */ + size = (size / PA_PAGE_SIZE) * PA_PAGE_SIZE; + #ifdef MADV_REMOVE if (madvise(ptr, size, MADV_REMOVE) >= 0) return; @@ -254,9 +262,9 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { #endif #ifdef MADV_DONTNEED - pa_assert_se(madvise(ptr, size, MADV_DONTNEED) == 0); + madvise(ptr, size, MADV_DONTNEED); #elif defined(POSIX_MADV_DONTNEED) - pa_assert_se(posix_madvise(ptr, size, POSIX_MADV_DONTNEED) == 0); + posix_madvise(ptr, size, POSIX_MADV_DONTNEED); #endif } @@ -283,7 +291,7 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { } if (st.st_size <= 0 || - st.st_size > (off_t) (MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker))) || + st.st_size > (off_t) (MAX_SHM_SIZE+SHM_MARKER_SIZE) || PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) { pa_log("Invalid shared memory segment size"); goto fail; @@ -291,13 +299,13 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { m->size = (size_t) st.st_size; - if ((m->ptr = mmap(NULL, m->size, PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { pa_log("mmap() failed: %s", pa_cstrerror(errno)); goto fail; } - m->do_unlink = 0; - m->shared = 1; + m->do_unlink = FALSE; + m->shared = TRUE; pa_assert_se(pa_close(fd) == 0); @@ -346,12 +354,12 @@ int pa_shm_cleanup(void) { if (pa_shm_attach_ro(&seg, id) < 0) continue; - if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) { + if (seg.size < SHM_MARKER_SIZE) { pa_shm_free(&seg); continue; } - m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker))); + m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - SHM_MARKER_SIZE); if (pa_atomic_load(&m->marker) != SHM_MARKER) { pa_shm_free(&seg); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 0229918c..0d05b00a 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -819,7 +819,7 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa usec = i->sink->fixed_latency; if (usec != (pa_usec_t) -1) - usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); i->thread_info.requested_sink_latency = usec; pa_sink_invalidate_requested_latency(i->sink); diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 30fa5579..13f0e11e 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -190,6 +190,7 @@ pa_sink* pa_sink_new( s->core = core; s->state = PA_SINK_INIT; s->flags = flags; + s->suspend_cause = 0; s->name = pa_xstrdup(name); s->proplist = pa_proplist_copy(data->proplist); s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); @@ -263,7 +264,9 @@ pa_sink* pa_sink_new( 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); + s->monitor_source = pa_source_new(core, &source_data, + ((flags & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) | + ((flags & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0)); pa_source_new_data_done(&source_data); @@ -349,31 +352,28 @@ void pa_sink_put(pa_sink* s) { pa_assert(s->rtpoll); pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); - if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) { + /* Generally, flags should be initialized via pa_sink_new(). As a + * special exception we allow volume related flags to be set + * between _new() and _put(). */ + + if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) s->flags |= PA_SINK_DECIBEL_VOLUME; - s->base_volume = PA_VOLUME_NORM; - } + + if ((s->flags & PA_SINK_DECIBEL_VOLUME) && s->core->flat_volumes) + s->flags |= PA_SINK_FLAT_VOLUME; s->thread_info.soft_volume = s->soft_volume; s->thread_info.soft_muted = s->muted; - if (s->flags & PA_SINK_DECIBEL_VOLUME) - s->n_volume_steps = PA_VOLUME_NORM+1; - - if (s->core->flat_volumes) - if (s->flags & PA_SINK_DECIBEL_VOLUME) - s->flags |= PA_SINK_FLAT_VOLUME; - - if (s->flags & PA_SINK_LATENCY) - s->monitor_source->flags |= PA_SOURCE_LATENCY; + pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SINK_DECIBEL_VOLUME)); + pa_assert(!(s->flags & PA_SINK_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1); + pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->fixed_latency != 0)); + pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY)); + pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY)); - if (s->flags & PA_SINK_DYNAMIC_LATENCY) { - s->monitor_source->flags |= PA_SOURCE_DYNAMIC_LATENCY; - s->fixed_latency = 0; - } else if (s->fixed_latency <= 0) - s->fixed_latency = DEFAULT_FIXED_LATENCY; - - s->monitor_source->fixed_latency = s->fixed_latency; + pa_assert(s->monitor_source->fixed_latency == s->fixed_latency); + pa_assert(s->monitor_source->thread_info.min_latency == s->thread_info.min_latency); + pa_assert(s->monitor_source->thread_info.max_latency == s->thread_info.max_latency); pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0); @@ -500,11 +500,25 @@ int pa_sink_update_status(pa_sink*s) { } /* Called from main context */ -int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) { +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_sink_assert_ref(s); pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(cause != 0); - if (suspend) + if (suspend) { + s->suspend_cause |= cause; + s->monitor_source->suspend_cause |= cause; + } else { + s->suspend_cause &= ~cause; + s->monitor_source->suspend_cause &= ~cause; + } + + if ((pa_sink_get_state(s) == PA_SINK_SUSPENDED) == !!s->suspend_cause) + return 0; + + pa_log_debug("Suspend cause of sink %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming"); + + if (s->suspend_cause) 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); @@ -788,11 +802,17 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) { - pa_memchunk_make_writable(result, 0); - if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) - pa_silence_memchunk(result, &s->sample_spec); - else + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) { + pa_memblock_unref(result->memblock); + pa_silence_memchunk_get(&s->core->silence_cache, + s->core->mempool, + result, + &s->sample_spec, + result->length); + } else { + pa_memchunk_make_writable(result, 0); pa_volume_memchunk(result, &s->sample_spec, &volume); + } } } else { void *ptr; @@ -933,22 +953,95 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { /* Called from IO thread context */ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { + pa_mix_info info[MAX_MIX_CHANNELS]; + size_t length1st = length; + unsigned n; + pa_sink_assert_ref(s); pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); pa_assert(length > 0); pa_assert(pa_frame_aligned(length, &s->sample_spec)); pa_assert(result); + pa_sink_ref(s); + pa_assert(!s->thread_info.rewind_requested); pa_assert(s->thread_info.rewind_nbytes == 0); - /*** This needs optimization ***/ + pa_assert(length > 0); - result->index = 0; - result->length = length; - result->memblock = pa_memblock_new(s->core->mempool, length); + n = fill_mix_info(s, &length1st, info, MAX_MIX_CHANNELS); - pa_sink_render_into_full(s, result); + if (n == 0) { + pa_silence_memchunk_get(&s->core->silence_cache, + s->core->mempool, + result, + &s->sample_spec, + length1st); + } else if (n == 1) { + pa_cvolume volume; + + *result = info[0].chunk; + pa_memblock_ref(result->memblock); + + if (result->length > length) + result->length = length; + + pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); + + if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) { + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) { + pa_memblock_unref(result->memblock); + pa_silence_memchunk_get(&s->core->silence_cache, + s->core->mempool, + result, + &s->sample_spec, + result->length); + } else { + pa_memchunk_make_writable(result, length); + pa_volume_memchunk(result, &s->sample_spec, &volume); + } + } + } else { + void *ptr; + + result->index = 0; + result->memblock = pa_memblock_new(s->core->mempool, length); + + ptr = pa_memblock_acquire(result->memblock); + + result->length = pa_mix(info, n, + (uint8_t*) ptr + result->index, length1st, + &s->sample_spec, + &s->thread_info.soft_volume, + s->thread_info.soft_muted); + + pa_memblock_release(result->memblock); + } + + inputs_drop(s, info, n, result); + + if (result->length < length) { + pa_memchunk chunk; + size_t l, d; + pa_memchunk_make_writable(result, length); + + l = length - result->length; + d = result->index + result->length; + while (l > 0) { + chunk = *result; + chunk.index = d; + chunk.length = l; + + pa_sink_render_into(s, &chunk); + + d += chunk.length; + l -= chunk.length; + } + result->length = length; + } + + pa_sink_unref(s); } /* Called from main thread */ @@ -1272,8 +1365,12 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) { pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0); - if (old_muted != s->muted) + if (old_muted != s->muted) { pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + /* Make sure the soft mute status stays in sync */ + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); + } } return s->muted; @@ -1744,17 +1841,18 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse } /* Called from main thread */ -int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) { +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_sink *sink; uint32_t idx; int ret = 0; pa_core_assert_ref(c); + pa_assert(cause != 0); for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx))) { int r; - if ((r = pa_sink_suspend(sink, suspend)) < 0) + if ((r = pa_sink_suspend(sink, suspend, cause)) < 0) ret = r; } @@ -1863,8 +1961,11 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { if (result != (pa_usec_t) -1) result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); - s->thread_info.requested_latency = result; - s->thread_info.requested_latency_valid = TRUE; + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + /* Only cache if properly initialized */ + s->thread_info.requested_latency = result; + s->thread_info.requested_latency_valid = TRUE; + } return result; } @@ -2050,6 +2151,22 @@ void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency); } +/* Called from main thread, before the sink is put */ +void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency) { + pa_sink_assert_ref(s); + + pa_assert(pa_sink_get_state(s) == PA_SINK_INIT); + + if (latency < ABSOLUTE_MIN_LATENCY) + latency = ABSOLUTE_MIN_LATENCY; + + if (latency > ABSOLUTE_MAX_LATENCY) + latency = ABSOLUTE_MAX_LATENCY; + + s->fixed_latency = latency; + pa_source_set_fixed_latency(s->monitor_source, latency); +} + /* Called from main context */ size_t pa_sink_get_max_rewind(pa_sink *s) { size_t r; @@ -2099,6 +2216,21 @@ pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink) { t = "multimedia-player"; else if (pa_streq(ff, "tv")) t = "video-display"; + + /* + * The following icons are not part of the icon naming spec, + * because Rodney Dawes sucks as the maintainer of that spec. + * + * http://lists.freedesktop.org/archives/xdg/2009-May/010397.html + */ + else if (pa_streq(ff, "headset")) + t = "audio-headset"; + else if (pa_streq(ff, "headphone")) + t = "audio-headphones"; + else if (pa_streq(ff, "speaker")) + t = "audio-speakers"; + else if (pa_streq(ff, "hands-free")) + t = "audio-handsfree"; } if (!t) diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 352282b8..4dce3f93 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -56,6 +56,7 @@ struct pa_sink { pa_core *core; pa_sink_state_t state; pa_sink_flags_t flags; + pa_suspend_cause_t suspend_cause; char *name; char *driver; /* may be NULL */ @@ -229,6 +230,7 @@ void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p); void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind); void pa_sink_set_max_request(pa_sink *s, size_t max_request); void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency); void pa_sink_detach(pa_sink *s); void pa_sink_attach(pa_sink *s); @@ -251,8 +253,8 @@ 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); +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause); +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause); void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume); void pa_sink_propagate_flat_volume(pa_sink *s); diff --git a/src/pulsecore/sndfile-util.c b/src/pulsecore/sndfile-util.c new file mode 100644 index 00000000..032aefca --- /dev/null +++ b/src/pulsecore/sndfile-util.c @@ -0,0 +1,462 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* Shared between pacat/parec/paplay and the server */ + +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "sndfile-util.h" + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss) { + SF_INFO sfi; + + pa_assert(sf); + pa_assert(ss); + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + switch (sfi.format & SF_FORMAT_SUBMASK) { + + case SF_FORMAT_PCM_16: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_S8: + ss->format = PA_SAMPLE_S16NE; + break; + + case SF_FORMAT_PCM_24: + case SF_FORMAT_PCM_32: + ss->format = PA_SAMPLE_S32NE; + break; + + case SF_FORMAT_ULAW: + ss->format = PA_SAMPLE_ULAW; + break; + + case SF_FORMAT_ALAW: + ss->format = PA_SAMPLE_ALAW; + break; + + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + default: + ss->format = PA_SAMPLE_FLOAT32NE; + break; + } + + ss->rate = (uint32_t) sfi.samplerate; + ss->channels = (uint8_t) sfi.channels; + + if (!pa_sample_spec_valid(ss)) + return -1; + + return 0; +} + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss) { + pa_assert(sfi); + pa_assert(ss); + + sfi->samplerate = (int) ss->rate; + sfi->channels = (int) ss->channels; + + if (pa_sample_format_is_le(ss->format) > 0) + sfi->format = SF_ENDIAN_LITTLE; + else if (pa_sample_format_is_be(ss->format) > 0) + sfi->format = SF_ENDIAN_BIG; + + switch (ss->format) { + + case PA_SAMPLE_U8: + ss->format = PA_SAMPLE_S16NE; + sfi->format = SF_FORMAT_PCM_U8; + break; + + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + ss->format = PA_SAMPLE_S16NE; + sfi->format |= SF_FORMAT_PCM_16; + break; + + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + ss->format = PA_SAMPLE_S32NE; + sfi->format |= SF_FORMAT_PCM_24; + break; + + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32RE: + ss->format = PA_SAMPLE_S32NE; + sfi->format |= SF_FORMAT_PCM_32; + break; + + case PA_SAMPLE_ULAW: + sfi->format = SF_FORMAT_ULAW; + break; + + case PA_SAMPLE_ALAW: + sfi->format = SF_FORMAT_ALAW; + break; + + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + default: + ss->format = PA_SAMPLE_FLOAT32NE; + sfi->format |= SF_FORMAT_FLOAT; + break; + } + + + if (!pa_sample_spec_valid(ss)) + return -1; + + return 0; +} + +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm) { + + static const pa_channel_position_t table[] = { + [SF_CHANNEL_MAP_MONO] = PA_CHANNEL_POSITION_MONO, + [SF_CHANNEL_MAP_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, /* libsndfile distuingishes left und front-left, which we don't */ + [SF_CHANNEL_MAP_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [SF_CHANNEL_MAP_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [SF_CHANNEL_MAP_FRONT_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, + [SF_CHANNEL_MAP_FRONT_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [SF_CHANNEL_MAP_FRONT_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [SF_CHANNEL_MAP_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER, + [SF_CHANNEL_MAP_REAR_LEFT] = PA_CHANNEL_POSITION_REAR_LEFT, + [SF_CHANNEL_MAP_REAR_RIGHT] = PA_CHANNEL_POSITION_REAR_RIGHT, + [SF_CHANNEL_MAP_LFE] = PA_CHANNEL_POSITION_LFE, + [SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + [SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + [SF_CHANNEL_MAP_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT, + [SF_CHANNEL_MAP_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT, + [SF_CHANNEL_MAP_TOP_CENTER] = PA_CHANNEL_POSITION_TOP_CENTER, + [SF_CHANNEL_MAP_TOP_FRONT_LEFT] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT, + [SF_CHANNEL_MAP_TOP_FRONT_RIGHT] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + [SF_CHANNEL_MAP_TOP_FRONT_CENTER] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + [SF_CHANNEL_MAP_TOP_REAR_LEFT] = PA_CHANNEL_POSITION_TOP_REAR_LEFT, + [SF_CHANNEL_MAP_TOP_REAR_RIGHT] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT, + [SF_CHANNEL_MAP_TOP_REAR_CENTER] = PA_CHANNEL_POSITION_TOP_REAR_CENTER + }; + + SF_INFO sfi; + int *channels; + unsigned c; + + pa_assert(sf); + pa_assert(cm); + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + channels = pa_xnew(int, sfi.channels); + if (!sf_command(sf, SFC_GET_CHANNEL_MAP_INFO, + channels, sizeof(channels[0]) * sfi.channels)) { + + pa_xfree(channels); + return -1; + } + + cm->channels = (uint8_t) sfi.channels; + for (c = 0; c < cm->channels; c++) { + if (channels[c] <= SF_CHANNEL_MAP_INVALID || + (unsigned) channels[c] >= PA_ELEMENTSOF(table)) { + pa_xfree(channels); + return -1; + } + + cm->map[c] = table[channels[c]]; + } + + pa_xfree(channels); + + if (!pa_channel_map_valid(cm)) + return -1; + + return 0; +} + +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm) { + static const int table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = SF_CHANNEL_MAP_MONO, + + [PA_CHANNEL_POSITION_FRONT_LEFT] = SF_CHANNEL_MAP_FRONT_LEFT, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SF_CHANNEL_MAP_FRONT_RIGHT, + [PA_CHANNEL_POSITION_FRONT_CENTER] = SF_CHANNEL_MAP_FRONT_CENTER, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SF_CHANNEL_MAP_REAR_CENTER, + [PA_CHANNEL_POSITION_REAR_LEFT] = SF_CHANNEL_MAP_REAR_LEFT, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SF_CHANNEL_MAP_REAR_RIGHT, + + [PA_CHANNEL_POSITION_LFE] = SF_CHANNEL_MAP_LFE, + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SF_CHANNEL_MAP_SIDE_LEFT, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SF_CHANNEL_MAP_SIDE_RIGHT, + + [PA_CHANNEL_POSITION_AUX0] = -1, + [PA_CHANNEL_POSITION_AUX1] = -1, + [PA_CHANNEL_POSITION_AUX2] = -1, + [PA_CHANNEL_POSITION_AUX3] = -1, + [PA_CHANNEL_POSITION_AUX4] = -1, + [PA_CHANNEL_POSITION_AUX5] = -1, + [PA_CHANNEL_POSITION_AUX6] = -1, + [PA_CHANNEL_POSITION_AUX7] = -1, + [PA_CHANNEL_POSITION_AUX8] = -1, + [PA_CHANNEL_POSITION_AUX9] = -1, + [PA_CHANNEL_POSITION_AUX10] = -1, + [PA_CHANNEL_POSITION_AUX11] = -1, + [PA_CHANNEL_POSITION_AUX12] = -1, + [PA_CHANNEL_POSITION_AUX13] = -1, + [PA_CHANNEL_POSITION_AUX14] = -1, + [PA_CHANNEL_POSITION_AUX15] = -1, + [PA_CHANNEL_POSITION_AUX16] = -1, + [PA_CHANNEL_POSITION_AUX17] = -1, + [PA_CHANNEL_POSITION_AUX18] = -1, + [PA_CHANNEL_POSITION_AUX19] = -1, + [PA_CHANNEL_POSITION_AUX20] = -1, + [PA_CHANNEL_POSITION_AUX21] = -1, + [PA_CHANNEL_POSITION_AUX22] = -1, + [PA_CHANNEL_POSITION_AUX23] = -1, + [PA_CHANNEL_POSITION_AUX24] = -1, + [PA_CHANNEL_POSITION_AUX25] = -1, + [PA_CHANNEL_POSITION_AUX26] = -1, + [PA_CHANNEL_POSITION_AUX27] = -1, + [PA_CHANNEL_POSITION_AUX28] = -1, + [PA_CHANNEL_POSITION_AUX29] = -1, + [PA_CHANNEL_POSITION_AUX30] = -1, + [PA_CHANNEL_POSITION_AUX31] = -1, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SF_CHANNEL_MAP_TOP_CENTER, + + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SF_CHANNEL_MAP_TOP_FRONT_LEFT, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SF_CHANNEL_MAP_TOP_FRONT_RIGHT, + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SF_CHANNEL_MAP_TOP_FRONT_CENTER , + + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SF_CHANNEL_MAP_TOP_REAR_LEFT, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SF_CHANNEL_MAP_TOP_REAR_RIGHT, + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SF_CHANNEL_MAP_TOP_REAR_CENTER, + }; + + int *channels; + unsigned c; + + pa_assert(sf); + pa_assert(cm); + + /* Suppress channel mapping for the obvious cases */ + if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_MONO) + return 0; + + if (cm->channels == 2 && + cm->map[0] == PA_CHANNEL_POSITION_FRONT_LEFT && + cm->map[1] == PA_CHANNEL_POSITION_FRONT_RIGHT) + return 0; + + channels = pa_xnew(int, cm->channels); + for (c = 0; c < cm->channels; c++) { + + if (cm->map[c] < 0 || + cm->map[c] >= PA_CHANNEL_POSITION_MAX || + table[cm->map[c]] < 0) { + pa_xfree(channels); + return -1; + } + + channels[c] = table[cm->map[c]]; + } + + if (!sf_command(sf, SFC_SET_CHANNEL_MAP_INFO, + channels, sizeof(channels[0]) * cm->channels)) { + pa_xfree(channels); + return -1; + } + + pa_xfree(channels); + return 0; +} + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p) { + + static const char* table[] = { + [SF_STR_TITLE] = PA_PROP_MEDIA_TITLE, + [SF_STR_COPYRIGHT] = PA_PROP_MEDIA_COPYRIGHT, + [SF_STR_SOFTWARE] = PA_PROP_MEDIA_SOFTWARE, + [SF_STR_ARTIST] = PA_PROP_MEDIA_ARTIST, + [SF_STR_COMMENT] = "media.comment", + [SF_STR_DATE] = "media.date" + }; + + SF_INFO sfi; + SF_FORMAT_INFO fi; + unsigned c; + + pa_assert(sf); + pa_assert(p); + + for (c = 0; c < PA_ELEMENTSOF(table); c++) { + const char *s; + char *t; + + if (!table[c]) + continue; + + if (!(s = sf_get_string(sf, c))) + continue; + + t = pa_utf8_filter(s); + pa_proplist_sets(p, table[c], t); + pa_xfree(t); + } + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + pa_zero(fi); + fi.format = sfi.format; + if (sf_command(sf, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name) { + char *t; + + t = pa_utf8_filter(fi.name); + pa_proplist_sets(p, "media.format", t); + pa_xfree(t); + } +} + +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss) { + pa_assert(ss); + + switch (ss->format) { + case PA_SAMPLE_S16NE: + return (pa_sndfile_readf_t) sf_readf_short; + + case PA_SAMPLE_S32NE: + return (pa_sndfile_readf_t) sf_readf_int; + + case PA_SAMPLE_FLOAT32NE: + return (pa_sndfile_readf_t) sf_readf_float; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + return NULL; + + default: + pa_assert_not_reached(); + } +} + +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss) { + pa_assert(ss); + + switch (ss->format) { + case PA_SAMPLE_S16NE: + return (pa_sndfile_writef_t) sf_writef_short; + + case PA_SAMPLE_S32NE: + return (pa_sndfile_writef_t) sf_writef_int; + + case PA_SAMPLE_FLOAT32NE: + return (pa_sndfile_writef_t) sf_writef_float; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + return NULL; + + default: + pa_assert_not_reached(); + } +} + +int pa_sndfile_format_from_string(const char *name) { + int i, count = 0; + + + if (!name[0]) + return -1; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + + /* First try to match via full type string */ + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + + if (strcasecmp(name, fi.name) == 0) + return i; + } + + /* Then, try to match via the full extension */ + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + + if (strcasecmp(name, fi.extension) == 0) + return i; + } + + /* Then, try to match via the start of the type string */ + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + + if (strncasecmp(name, fi.extension, strlen(name)) == 0) + return i; + } + + return -1; +} + +void pa_sndfile_dump_formats(void) { + int i, count = 0; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + printf("%s\t%s\n", fi.extension, fi.name); + } +} diff --git a/src/pulsecore/sndfile-util.h b/src/pulsecore/sndfile-util.h new file mode 100644 index 00000000..74021da1 --- /dev/null +++ b/src/pulsecore/sndfile-util.h @@ -0,0 +1,52 @@ +#ifndef foopulsecoresndfileutilhfoo +#define foopulsecoresndfileutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <sndfile.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/proplist.h> + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss); +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm); + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss); +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm); + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p); + +typedef sf_count_t (*pa_sndfile_readf_t)(SNDFILE *sndfile, void *ptr, sf_count_t frames); +typedef sf_count_t (*pa_sndfile_writef_t)(SNDFILE *sndfile, const void *ptr, sf_count_t frames); + +/* Returns NULL if sf_read_raw() shall be used */ +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss); + +/* Returns NULL if sf_write_raw() shall be used */ +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss); + +int pa_sndfile_format_from_string(const char *extension); + +void pa_sndfile_dump_formats(void); + +#endif diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index 3453637f..502e5c69 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -41,6 +41,7 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/core-util.h> #include <pulsecore/sample-util.h> +#include <pulsecore/sndfile-util.h> #include "sound-file-stream.h" @@ -234,10 +235,11 @@ int pa_play_file( const pa_cvolume *volume) { file_stream *u = NULL; - SF_INFO sfinfo; pa_sample_spec ss; + pa_channel_map cm; pa_sink_input_new_data data; int fd; + SF_INFO sfi; pa_assert(sink); pa_assert(fname); @@ -251,8 +253,6 @@ int pa_play_file( u->readf_function = NULL; u->memblockq = NULL; - memset(&sfinfo, 0, sizeof(sfinfo)); - if ((fd = open(fname, O_RDONLY #ifdef O_NOCTTY |O_NOCTTY @@ -281,50 +281,36 @@ int pa_play_file( pa_log_debug("POSIX_FADV_WILLNEED succeeded."); #endif - if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_zero(sfi); + if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfi, 1))) { pa_log("Failed to open file %s", fname); - pa_close(fd); goto fail; } - switch (sfinfo.format & 0xFF) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss.format = PA_SAMPLE_S16NE; - u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short; - break; - - case SF_FORMAT_ULAW: - ss.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss.format = PA_SAMPLE_ALAW; - break; + fd = -1; - case SF_FORMAT_FLOAT: - default: - ss.format = PA_SAMPLE_FLOAT32NE; - u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float; - break; + if (pa_sndfile_read_sample_spec(u->sndfile, &ss) < 0) { + pa_log("Failed to determine file sample format."); + goto fail; } - ss.rate = (uint32_t) sfinfo.samplerate; - ss.channels = (uint8_t) sfinfo.channels; - - if (!pa_sample_spec_valid(&ss)) { - pa_log("Unsupported sample format in file %s", fname); - goto fail; + if (pa_sndfile_read_channel_map(u->sndfile, &cm) < 0) { + if (ss.channels > 2) + pa_log_info("Failed to determine file channel map, synthesizing one."); + pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); } + u->readf_function = pa_sndfile_readf_function(&ss); + pa_sink_input_new_data_init(&data); data.sink = sink; data.driver = __FILE__; pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_channel_map(&data, &cm); pa_sink_input_new_data_set_volume(&data, volume); pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname)); pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname); + pa_sndfile_init_proplist(u->sndfile, data.proplist); pa_sink_input_new(&u->sink_input, sink->core, &data, 0); pa_sink_input_new_data_done(&data); @@ -352,5 +338,8 @@ fail: if (u) file_stream_unref(u); + if (fd >= 0) + pa_close(fd); + return -1; } diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c index db75ae08..2d9b76ad 100644 --- a/src/pulsecore/sound-file.c +++ b/src/pulsecore/sound-file.c @@ -35,19 +35,21 @@ #include <pulsecore/macro.h> #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sndfile-util.h> #include "sound-file.h" -#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) { + pa_memchunk *chunk, + pa_proplist *p) { SNDFILE *sf = NULL; - SF_INFO sfinfo; + SF_INFO sfi; int ret = -1; size_t l; sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames) = NULL; @@ -59,7 +61,6 @@ int pa_sound_file_load( pa_assert(chunk); pa_memchunk_reset(chunk); - memset(&sfinfo, 0, sizeof(sfinfo)); if ((fd = open(fname, O_RDONLY #ifdef O_NOCTTY @@ -78,48 +79,29 @@ int pa_sound_file_load( pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded."); #endif - if (!(sf = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_zero(sfi); + if (!(sf = sf_open_fd(fd, SFM_READ, &sfi, 1))) { pa_log("Failed to open file %s", fname); - pa_close(fd); goto finish; } - switch (sfinfo.format & SF_FORMAT_SUBMASK) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss->format = PA_SAMPLE_S16NE; - readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_short; - break; - - case SF_FORMAT_ULAW: - ss->format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss->format = PA_SAMPLE_ALAW; - break; - - case SF_FORMAT_FLOAT: - case SF_FORMAT_DOUBLE: - default: - ss->format = PA_SAMPLE_FLOAT32NE; - readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_float; - break; - } - - ss->rate = (uint32_t) sfinfo.samplerate; - ss->channels = (uint8_t) sfinfo.channels; + fd = -1; - if (!pa_sample_spec_valid(ss)) { - pa_log("Unsupported sample format in file %s", fname); + if (pa_sndfile_read_sample_spec(sf, ss) < 0) { + pa_log("Failed to determine file sample format."); goto finish; } - if (map) + if ((map && pa_sndfile_read_channel_map(sf, map) < 0)) { + if (ss->channels > 2) + pa_log("Failed to determine file channel map, synthesizing one."); pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_DEFAULT); + } - if ((l = pa_frame_size(ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + if (p) + pa_sndfile_init_proplist(sf, p); + + if ((l = pa_frame_size(ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { pa_log("File too large"); goto finish; } @@ -128,9 +110,11 @@ int pa_sound_file_load( chunk->index = 0; chunk->length = l; + readf_function = pa_sndfile_readf_function(ss); + ptr = pa_memblock_acquire(chunk->memblock); - if ((readf_function && readf_function(sf, ptr, sfinfo.frames) != sfinfo.frames) || + if ((readf_function && readf_function(sf, ptr, sfi.frames) != sfi.frames) || (!readf_function && sf_read_raw(sf, ptr, (sf_count_t) l) != (sf_count_t) l)) { pa_log("Premature file end"); goto finish; @@ -149,55 +133,35 @@ finish: if (ret != 0 && chunk->memblock) pa_memblock_unref(chunk->memblock); + if (fd >= 0) + pa_close(fd); + return ret; } int pa_sound_file_too_big_to_cache(const char *fname) { SNDFILE*sf = NULL; - SF_INFO sfinfo; + SF_INFO sfi; pa_sample_spec ss; pa_assert(fname); - if (!(sf = sf_open(fname, SFM_READ, &sfinfo))) { + pa_zero(sfi); + if (!(sf = sf_open(fname, SFM_READ, &sfi))) { pa_log("Failed to open file %s", fname); return -1; } - sf_close(sf); - - switch (sfinfo.format & SF_FORMAT_SUBMASK) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss.format = PA_SAMPLE_S16NE; - break; - - case SF_FORMAT_ULAW: - ss.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss.format = PA_SAMPLE_ALAW; - break; - - case SF_FORMAT_DOUBLE: - case SF_FORMAT_FLOAT: - default: - ss.format = PA_SAMPLE_FLOAT32NE; - break; - } - - ss.rate = (uint32_t) sfinfo.samplerate; - ss.channels = (uint8_t) sfinfo.channels; - - if (!pa_sample_spec_valid(&ss)) { - pa_log("Unsupported sample format in file %s", fname); + if (pa_sndfile_read_sample_spec(sf, &ss) < 0) { + pa_log("Failed to determine file sample format."); + sf_close(sf); return -1; } - if ((pa_frame_size(&ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + sf_close(sf); + + if ((pa_frame_size(&ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { pa_log("File too large: %s", fname); return 1; } diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h index 34e02616..4dc0c7e1 100644 --- a/src/pulsecore/sound-file.h +++ b/src/pulsecore/sound-file.h @@ -26,7 +26,7 @@ #include <pulse/channelmap.h> #include <pulsecore/memchunk.h> -int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk); +int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk, pa_proplist *p); int pa_sound_file_too_big_to_cache(const char *fname); diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 21902509..53697c57 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -180,6 +180,7 @@ pa_source* pa_source_new( s->core = core; s->state = PA_SOURCE_INIT; s->flags = flags; + s->suspend_cause = 0; s->name = pa_xstrdup(name); s->proplist = pa_proplist_copy(data->proplist); s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); @@ -308,20 +309,19 @@ void pa_source_put(pa_source *s) { pa_assert(s->rtpoll); pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); - if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) { - s->flags |= PA_SOURCE_DECIBEL_VOLUME; + /* Generally, flags should be initialized via pa_source_new(). As + * a special exception we allow volume related flags to be set + * between _new() and _put(). */ - s->thread_info.soft_volume = s->soft_volume; - s->thread_info.soft_muted = s->muted; - } + if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) + s->flags |= PA_SOURCE_DECIBEL_VOLUME; - if (s->flags & PA_SOURCE_DECIBEL_VOLUME) - s->n_volume_steps = PA_VOLUME_NORM+1; + s->thread_info.soft_volume = s->soft_volume; + s->thread_info.soft_muted = s->muted; - if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) - s->fixed_latency = 0; - else if (s->fixed_latency <= 0) - s->fixed_latency = DEFAULT_FIXED_LATENCY; + pa_assert((s->flags & PA_SOURCE_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SOURCE_DECIBEL_VOLUME)); + pa_assert(!(s->flags & PA_SOURCE_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1); + pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == (s->fixed_latency != 0)); pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0); @@ -428,14 +428,25 @@ int pa_source_update_status(pa_source*s) { } /* Called from main context */ -int pa_source_suspend(pa_source *s, pa_bool_t suspend) { +int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_source_assert_ref(s); pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(cause != 0); if (s->monitor_of) return -PA_ERR_NOTSUPPORTED; if (suspend) + s->suspend_cause |= cause; + else + s->suspend_cause &= ~cause; + + if ((pa_source_get_state(s) == PA_SOURCE_SUSPENDED) == !!s->suspend_cause) + return 0; + + pa_log_debug("Suspend cause of source %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming"); + + 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); @@ -757,8 +768,12 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) { pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0); - if (old_muted != s->muted) + if (old_muted != s->muted) { pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + /* Make sure the soft mute status stays in sync */ + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); + } } return s->muted; @@ -1033,12 +1048,13 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ } /* Called from main thread */ -int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) { +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) { uint32_t idx; pa_source *source; int ret = 0; pa_core_assert_ref(c); + pa_assert(cause != 0); for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx))) { int r; @@ -1046,7 +1062,7 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) { if (source->monitor_of) continue; - if ((r = pa_source_suspend(source, suspend)) < 0) + if ((r = pa_source_suspend(source, suspend, cause)) < 0) ret = r; } @@ -1118,8 +1134,11 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { if (result != (pa_usec_t) -1) result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); - s->thread_info.requested_latency = result; - s->thread_info.requested_latency_valid = TRUE; + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + /* Only cache this if we are fully set up */ + s->thread_info.requested_latency = result; + s->thread_info.requested_latency_valid = TRUE; + } return result; } @@ -1276,6 +1295,21 @@ void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_laten pa_source_invalidate_requested_latency(s); } +/* Called from main thread, before the source is put */ +void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency) { + pa_source_assert_ref(s); + + pa_assert(pa_source_get_state(s) == PA_SOURCE_INIT); + + if (latency < ABSOLUTE_MIN_LATENCY) + latency = ABSOLUTE_MIN_LATENCY; + + if (latency > ABSOLUTE_MAX_LATENCY) + latency = ABSOLUTE_MAX_LATENCY; + + s->fixed_latency = latency; +} + /* Called from main thread */ size_t pa_source_get_max_rewind(pa_source *s) { size_t r; diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index b502c228..1fbed70e 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -58,6 +58,7 @@ struct pa_source { pa_core *core; pa_source_state_t state; pa_source_flags_t flags; + pa_suspend_cause_t suspend_cause; char *name; char *driver; /* may be NULL */ @@ -210,6 +211,7 @@ void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p); void pa_source_set_max_rewind(pa_source *s, size_t max_rewind); void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency); void pa_source_detach(pa_source *s); void pa_source_attach(pa_source *s); @@ -230,8 +232,8 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t size_t pa_source_get_max_rewind(pa_source *s); int pa_source_update_status(pa_source*s); -int pa_source_suspend(pa_source *s, pa_bool_t suspend); -int pa_source_suspend_all(pa_core *c, pa_bool_t suspend); +int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause); +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause); void pa_source_set_volume(pa_source *source, const pa_cvolume *volume); const pa_cvolume *pa_source_get_volume(pa_source *source, pa_bool_t force_refresh); diff --git a/src/pulsecore/strbuf.c b/src/pulsecore/strbuf.c index 9f5a84b4..4fc82ded 100644 --- a/src/pulsecore/strbuf.c +++ b/src/pulsecore/strbuf.c @@ -113,6 +113,13 @@ void pa_strbuf_puts(pa_strbuf *sb, const char *t) { pa_strbuf_putsn(sb, t, strlen(t)); } +/* Append a character to the string buffer */ +void pa_strbuf_putc(pa_strbuf *sb, char c) { + pa_assert(sb); + + pa_strbuf_putsn(sb, &c, 1); +} + /* Append a new chunk to the linked list */ static void append(pa_strbuf *sb, struct chunk *c) { pa_assert(sb); diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h index 05e69e03..d71ecb98 100644 --- a/src/pulsecore/strbuf.h +++ b/src/pulsecore/strbuf.h @@ -35,6 +35,7 @@ char *pa_strbuf_tostring_free(pa_strbuf *sb); size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); void pa_strbuf_puts(pa_strbuf *sb, const char *t); void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m); +void pa_strbuf_putc(pa_strbuf *sb, char c); pa_bool_t pa_strbuf_isempty(pa_strbuf *sb); diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c index cbafbba6..0f4ca867 100644 --- a/src/pulsecore/strlist.c +++ b/src/pulsecore/strlist.c @@ -159,3 +159,15 @@ pa_strlist *pa_strlist_reverse(pa_strlist *l) { return r; } + +pa_strlist *pa_strlist_next(pa_strlist *s) { + pa_assert(s); + + return s->next; +} + +const char *pa_strlist_data(pa_strlist *s) { + pa_assert(s); + + return ITEM_TO_TEXT(s); +} diff --git a/src/pulsecore/strlist.h b/src/pulsecore/strlist.h index 2584e86c..e57203c5 100644 --- a/src/pulsecore/strlist.h +++ b/src/pulsecore/strlist.h @@ -47,4 +47,10 @@ pa_strlist* pa_strlist_parse(const char *s); /* Reverse string list */ pa_strlist *pa_strlist_reverse(pa_strlist *l); +/* Return the next item in the list */ +pa_strlist *pa_strlist_next(pa_strlist *s); + +/* Return the string associated to the current item */ +const char *pa_strlist_data(pa_strlist *s); + #endif diff --git a/src/tests/proplist-test.c b/src/tests/proplist-test.c index 3e723561..27a0d3fe 100644 --- a/src/tests/proplist-test.c +++ b/src/tests/proplist-test.c @@ -27,11 +27,14 @@ #include <pulse/xmalloc.h> #include <pulsecore/macro.h> #include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> int main(int argc, char*argv[]) { + pa_modargs *ma; pa_proplist *a, *b, *c, *d; char *s, *t, *u, *v; const char *text; + const char *x[] = { "foo", NULL }; a = pa_proplist_new(); pa_assert_se(pa_proplist_sets(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0); @@ -78,5 +81,16 @@ int main(int argc, char*argv[]) { printf("%s\n", v); pa_xfree(v); + pa_assert_se(ma = pa_modargs_new("foo='foobar=waldo foo2=\"lj\\\\\"dhflh\" foo3=\\'kjlskj\\\\\\'\\''", x)); + pa_assert_se(a = pa_proplist_new()); + + pa_assert_se(pa_modargs_get_proplist(ma, "foo", a, PA_UPDATE_REPLACE) >= 0); + + printf("%s\n", v = pa_proplist_to_string(a)); + pa_xfree(v); + + pa_proplist_free(a); + pa_modargs_free(ma); + return 0; } diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c index 6b4a64ca..7236265a 100644 --- a/src/tests/resampler-test.c +++ b/src/tests/resampler-test.c @@ -34,12 +34,6 @@ #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; @@ -54,7 +48,7 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { uint8_t *u = d; for (i = 0; i < chunk->length / pa_frame_size(ss); i++) - printf("0x%02x ", *(u++)); + printf(" 0x%02x ", *(u++)); break; } @@ -64,7 +58,7 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { uint16_t *u = d; for (i = 0; i < chunk->length / pa_frame_size(ss); i++) - printf("0x%04x ", *(u++)); + printf(" 0x%04x ", *(u++)); break; } @@ -79,18 +73,40 @@ static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { break; } + case PA_SAMPLE_S24_32NE: + case PA_SAMPLE_S24_32RE: { + 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)); + printf("%4.3g ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : PA_FLOAT32_SWAP(*u)); u++; } break; } + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24BE: { + uint8_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) { + printf(" 0x%06x ", PA_READ24NE(u)); + u += pa_frame_size(ss); + } + + break; + } + default: pa_assert_not_reached(); } @@ -162,6 +178,23 @@ static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) { break; } + case PA_SAMPLE_S24_32NE: + case PA_SAMPLE_S24_32RE: { + uint32_t *u = d; + + u[0] = 0x000001; + u[1] = 0xFF0002; + u[2] = 0x7F0003; + u[3] = 0x800004; + u[4] = 0x9f0005; + u[5] = 0x3f0006; + u[6] = 0x107; + u[7] = 0xF00008; + u[8] = 0x2009; + u[9] = 0x210A; + break; + } + case PA_SAMPLE_FLOAT32NE: case PA_SAMPLE_FLOAT32RE: { float *u = d; @@ -179,11 +212,28 @@ static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) { if (ss->format == PA_SAMPLE_FLOAT32RE) for (i = 0; i < 10; i++) - u[i] = swap_float(u[i]); + u[i] = PA_FLOAT32_SWAP(u[i]); break; } + case PA_SAMPLE_S24NE: + case PA_SAMPLE_S24RE: { + uint8_t *u = d; + + PA_WRITE24NE(u, 0x000001); + PA_WRITE24NE(u+3, 0xFF0002); + PA_WRITE24NE(u+6, 0x7F0003); + PA_WRITE24NE(u+9, 0x800004); + PA_WRITE24NE(u+12, 0x9f0005); + PA_WRITE24NE(u+15, 0x3f0006); + PA_WRITE24NE(u+18, 0x107); + PA_WRITE24NE(u+21, 0xF00008); + PA_WRITE24NE(u+24, 0x2009); + PA_WRITE24NE(u+27, 0x210A); + break; + } + default: pa_assert_not_reached(); } @@ -211,7 +261,6 @@ int main(int argc, char *argv[]) { 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; @@ -229,14 +278,18 @@ int main(int argc, char *argv[]) { pa_resampler_run(forth, &i, &j); pa_resampler_run(back, &j, &k); + printf("before: "); dump_block(&a, &i); + printf("after : "); dump_block(&b, &j); + printf("reverse: "); dump_block(&a, &k); pa_memblock_unref(j.memblock); pa_memblock_unref(k.memblock); pa_volume_memchunk(&i, &a, &v); + printf("volume: "); dump_block(&a, &i); pa_memblock_unref(i.memblock); diff --git a/src/tests/rtstutter.c b/src/tests/rtstutter.c index f04d43af..a4b5d596 100644 --- a/src/tests/rtstutter.c +++ b/src/tests/rtstutter.c @@ -93,6 +93,8 @@ static void* work(void *p) { int main(int argc, char*argv[]) { unsigned n; + pa_log_set_level(PA_LOG_DEBUG); + srand((unsigned) time(NULL)); if (argc >= 3) { diff --git a/src/tests/volume-ui.py b/src/tests/volume-ui.py index 6dc1c47d..7909b801 100644 --- a/src/tests/volume-ui.py +++ b/src/tests/volume-ui.py @@ -111,6 +111,10 @@ class CVolume(Structure): _set_fade.restype = c_void_p _set_fade.argtypes = [c_void_p, c_void_p, c_float] + _to_dB = libpulse.pa_sw_volume_to_dB + _to_dB.restype = c_double + _to_dB.argytpes = [c_uint32] + def snprint(this): s = create_string_buffer(320) r = this._snprint(s, len(s), byref(this)) @@ -138,6 +142,12 @@ class CVolume(Structure): def set_fade(this, cm, f): return this._set_fade(byref(this), byref(cm), f) + def to_dB(this, channel = None): + if channel is None: + return this._to_dB(this.max()) + + return this._to_dB(this.values[channel]) + cm = ChannelMap() if len(sys.argv) > 1: @@ -149,7 +159,7 @@ v = CVolume() v.channels = cm.channels for i in range(cm.channels): - v.values[i] = 65536/2 + v.values[i] = 65536 title = cm.to_pretty_name() if title is None: @@ -163,6 +173,7 @@ vbox = gtk.VBox(spacing=6) channel_labels = {} channel_scales = {} +channel_dB_labels = {} def update_volume(update_channels = True, update_fade = True, update_balance = True, update_scale = True): if update_channels: @@ -178,6 +189,11 @@ def update_volume(update_channels = True, update_fade = True, update_balance = T if update_fade: fade_scale.set_value(v.get_fade(cm)) + for i in range(cm.channels): + channel_dB_labels[i].set_label("%0.2f dB" % v.to_dB(i)) + + value_dB_label.set_label("%0.2f dB" % v.to_dB()) + def fade_value_changed(fs): v.set_fade(cm, fade_scale.get_value()) update_volume(update_fade = False) @@ -200,19 +216,26 @@ for i in range(cm.channels): vbox.pack_start(channel_labels[i], expand=False, fill=True) channel_scales[i] = gtk.HScale() - channel_scales[i].set_range(0, 65536) + channel_scales[i].set_range(0, 65536*3/2) channel_scales[i].set_digits(0) channel_scales[i].set_value_pos(gtk.POS_RIGHT) vbox.pack_start(channel_scales[i], expand=False, fill=True) + channel_dB_labels[i] = gtk.Label("-xxx dB") + channel_dB_labels[i].set_alignment(1, 1) + vbox.pack_start(channel_dB_labels[i], expand=False, fill=True) + value_label = gtk.Label("Value") value_label.set_alignment(0, .5) vbox.pack_start(value_label, expand=False, fill=True) value_scale = gtk.HScale() -value_scale.set_range(0, 65536) +value_scale.set_range(0, 65536*3/2) value_scale.set_value_pos(gtk.POS_RIGHT) value_scale.set_digits(0) vbox.pack_start(value_scale, expand=False, fill=True) +value_dB_label = gtk.Label("-xxx dB") +value_dB_label.set_alignment(1, 1) +vbox.pack_start(value_dB_label, expand=False, fill=True) balance_label = gtk.Label("Balance") balance_label.set_alignment(0, .5) diff --git a/src/utils/pabrowse.c b/src/utils/pabrowse.c index 288d44a9..a6487b88 100644 --- a/src/utils/pabrowse.c +++ b/src/utils/pabrowse.c @@ -30,6 +30,8 @@ #include <pulse/pulseaudio.h> #include <pulse/browser.h> +#include <pulsecore/core-util.h> + static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { fprintf(stderr, "Got signal, exiting\n"); m->quit(m, 0); @@ -127,7 +129,7 @@ int main(int argc, char *argv[]) { assert(r == 0); pa_signal_new(SIGINT, exit_signal_callback, NULL); pa_signal_new(SIGTERM, exit_signal_callback, NULL); - signal(SIGPIPE, SIG_IGN); + pa_disable_sigpipe(); 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); diff --git a/src/utils/pacat.c b/src/utils/pacat.c index 15e842f3..0b6df3d8 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -35,9 +35,16 @@ #include <fcntl.h> #include <locale.h> +#include <sndfile.h> + #include <pulse/i18n.h> #include <pulse/pulseaudio.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/sndfile-util.h> + #define TIME_EVENT_USEC 50000 #define CLEAR_LINE "\x1B[K" @@ -53,35 +60,92 @@ static size_t buffer_length = 0, buffer_index = 0; static pa_io_event* stdio_event = NULL; -static char *stream_name = NULL, *client_name = NULL, *device = NULL; +static pa_proplist *proplist = NULL; +static char *device = NULL; + +static SNDFILE* sndfile = NULL; -static int verbose = 0; +static pa_bool_t verbose = FALSE; static pa_volume_t volume = PA_VOLUME_NORM; -static int volume_is_set = 0; +static pa_bool_t volume_is_set = FALSE; static pa_sample_spec sample_spec = { .format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2 }; +static pa_bool_t sample_spec_set = FALSE; static pa_channel_map channel_map; -static int channel_map_set = 0; +static pa_bool_t channel_map_set = FALSE; + +static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL; +static sf_count_t (*writef_function)(SNDFILE *_sndfile, const void *ptr, sf_count_t frames) = NULL; static pa_stream_flags_t flags = 0; -static size_t latency = 0, process_time=0; +static size_t latency = 0, process_time = 0; + +static pa_bool_t raw = TRUE; +static int file_format = -1; /* A shortcut for terminating the application */ static void quit(int ret) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->quit(mainloop_api, ret); } +/* Connection draining complete */ +static void context_drain_complete(pa_context*c, void *userdata) { + pa_context_disconnect(c); +} + +/* Stream draining complete */ +static void stream_drain_complete(pa_stream*s, int success, void *userdata) { + + if (!success) { + pa_log(_("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + } + + if (verbose) + pa_log(_("Playback stream drained.\n")); + + pa_stream_disconnect(stream); + pa_stream_unref(stream); + stream = NULL; + + if (!pa_context_drain(context, context_drain_complete, NULL)) + pa_context_disconnect(context); + else { + if (verbose) + pa_log(_("Draining connection to server.\n")); + } +} + +/* Start draining */ +static void start_drain(void) { + + if (stream) { + pa_operation *o; + + pa_stream_set_write_callback(stream, NULL, NULL); + + if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) { + pa_log(_("pa_stream_drain(): %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + pa_operation_unref(o); + } else + quit(0); +} + /* Write some data to the stream */ static void do_stream_write(size_t length) { size_t l; - assert(length); + pa_assert(length); if (!buffer || !buffer_length) return; @@ -91,7 +155,7 @@ static void do_stream_write(size_t 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))); + pa_log(_("pa_stream_write() failed: %s\n"), pa_strerror(pa_context_errno(context))); quit(1); return; } @@ -108,53 +172,121 @@ 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); - assert(length > 0); + pa_assert(s); + pa_assert(length > 0); - if (stdio_event) - mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); + if (raw) { + pa_assert(!sndfile); - if (!buffer) - return; + if (stdio_event) + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); + + if (!buffer) + return; + + do_stream_write(length); + + } else { + sf_count_t bytes; + void *data; + + pa_assert(sndfile); + + data = pa_xmalloc(length); + + if (readf_function) { + size_t k = pa_frame_size(&sample_spec); - do_stream_write(length); + if ((bytes = readf_function(sndfile, data, (sf_count_t) (length/k))) > 0) + bytes *= (sf_count_t) k; + + } else + bytes = sf_read_raw(sndfile, data, (sf_count_t) length); + + if (bytes > 0) + pa_stream_write(s, data, (size_t) bytes, pa_xfree, 0, PA_SEEK_RELATIVE); + else + pa_xfree(data); + + if (bytes < (sf_count_t) length) + start_drain(); + } } /* This is called whenever new data may is available */ static void stream_read_callback(pa_stream *s, size_t length, void *userdata) { - const void *data; - assert(s); - assert(length > 0); - if (stdio_event) - mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); + pa_assert(s); + pa_assert(length > 0); - if (pa_stream_peek(s, &data, &length) < 0) { - fprintf(stderr, _("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - return; - } + if (raw) { + pa_assert(!sndfile); - assert(data); - assert(length > 0); + if (stdio_event) + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); + + + while (pa_stream_readable_size(s) > 0) { + const void *data; + + if (pa_stream_peek(s, &data, &length) < 0) { + pa_log(_("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + pa_assert(data); + pa_assert(length > 0); + + if (buffer) { + buffer = pa_xrealloc(buffer, buffer_length + length); + memcpy((uint8_t*) buffer + buffer_length, data, length); + buffer_length += length; + } else { + buffer = pa_xmalloc(length); + memcpy(buffer, data, length); + buffer_length = length; + buffer_index = 0; + } + pa_stream_drop(s); + } - if (buffer) { - buffer = pa_xrealloc(buffer, buffer_length + length); - memcpy((uint8_t*) buffer + buffer_length, data, length); - buffer_length += length; } else { - buffer = pa_xmalloc(length); - memcpy(buffer, data, length); - buffer_length = length; - buffer_index = 0; - } + pa_assert(sndfile); + + while (pa_stream_readable_size(s) > 0) { + sf_count_t bytes; + const void *data; + + if (pa_stream_peek(s, &data, &length) < 0) { + pa_log(_("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + pa_assert(data); + pa_assert(length > 0); + + if (writef_function) { + size_t k = pa_frame_size(&sample_spec); - pa_stream_drop(s); + if ((bytes = writef_function(sndfile, data, (sf_count_t) (length/k))) > 0) + bytes *= (sf_count_t) k; + + } else + bytes = sf_write_raw(sndfile, data, (sf_count_t) length); + + if (bytes < (sf_count_t) length) + quit(1); + + pa_stream_drop(s); + } + } } /* This routine is called whenever the stream state changes */ static void stream_state_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); switch (pa_stream_get_state(s)) { case PA_STREAM_CREATING: @@ -162,29 +294,30 @@ static void stream_state_callback(pa_stream *s, void *userdata) { break; case PA_STREAM_READY: + if (verbose) { const pa_buffer_attr *a; char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; - fprintf(stderr, _("Stream successfully created.\n")); + pa_log(_("Stream successfully created.\n")); if (!(a = pa_stream_get_buffer_attr(s))) - fprintf(stderr, _("pa_stream_get_buffer_attr() failed: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_log(_("pa_stream_get_buffer_attr() failed: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); else { if (mode == PLAYBACK) - fprintf(stderr, _("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n"), a->maxlength, a->tlength, a->prebuf, a->minreq); + pa_log(_("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n"), a->maxlength, a->tlength, a->prebuf, a->minreq); else { - assert(mode == RECORD); - fprintf(stderr, _("Buffer metrics: maxlength=%u, fragsize=%u\n"), a->maxlength, a->fragsize); + pa_assert(mode == RECORD); + pa_log(_("Buffer metrics: maxlength=%u, fragsize=%u\n"), a->maxlength, a->fragsize); } } - fprintf(stderr, _("Using sample spec '%s', channel map '%s'.\n"), + pa_log(_("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_log(_("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 "); @@ -194,72 +327,72 @@ static void stream_state_callback(pa_stream *s, void *userdata) { case PA_STREAM_FAILED: default: - fprintf(stderr, _("Stream error: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_log(_("Stream error: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); quit(1); } } static void stream_suspended_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) { if (pa_stream_is_suspended(s)) - fprintf(stderr, _("Stream device suspended.%s \n"), CLEAR_LINE); + pa_log(_("Stream device suspended.%s \n"), CLEAR_LINE); else - fprintf(stderr, _("Stream device resumed.%s \n"), CLEAR_LINE); + pa_log(_("Stream device resumed.%s \n"), CLEAR_LINE); } } static void stream_underflow_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream underrun.%s \n"), CLEAR_LINE); + pa_log(_("Stream underrun.%s \n"), CLEAR_LINE); } static void stream_overflow_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream overrun.%s \n"), CLEAR_LINE); + pa_log(_("Stream overrun.%s \n"), CLEAR_LINE); } static void stream_started_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream started.%s \n"), CLEAR_LINE); + pa_log(_("Stream started.%s \n"), CLEAR_LINE); } static void stream_moved_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream moved to device %s (%u, %ssuspended).%s \n"), pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : _("not "), CLEAR_LINE); + pa_log(_("Stream moved to device %s (%u, %ssuspended).%s \n"), pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : _("not "), CLEAR_LINE); } static void stream_buffer_attr_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream buffer attributes changed.%s \n"), CLEAR_LINE); + pa_log(_("Stream buffer attributes changed.%s \n"), CLEAR_LINE); } static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *pl, void *userdata) { char *t; - assert(s); - assert(name); - assert(pl); + pa_assert(s); + pa_assert(name); + pa_assert(pl); t = pa_proplist_to_string_sep(pl, ", "); - fprintf(stderr, "Got event '%s', properties '%s'\n", name, t); + pa_log("Got event '%s', properties '%s'\n", name, t); pa_xfree(t); } /* This is called whenever the context status changes */ static void context_state_callback(pa_context *c, void *userdata) { - assert(c); + pa_assert(c); switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: @@ -271,14 +404,14 @@ static void context_state_callback(pa_context *c, void *userdata) { int r; pa_buffer_attr buffer_attr; - assert(c); - assert(!stream); + pa_assert(c); + pa_assert(!stream); if (verbose) - fprintf(stderr, _("Connection established.%s \n"), CLEAR_LINE); + pa_log(_("Connection established.%s \n"), CLEAR_LINE); - 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))); + if (!(stream = pa_stream_new_with_proplist(c, NULL, &sample_spec, &channel_map, proplist))) { + pa_log(_("pa_stream_new() failed: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } @@ -306,13 +439,13 @@ static void context_state_callback(pa_context *c, void *userdata) { if (mode == PLAYBACK) { pa_cvolume cv; if ((r = pa_stream_connect_playback(stream, device, latency > 0 ? &buffer_attr : NULL, flags, volume_is_set ? pa_cvolume_set(&cv, sample_spec.channels, volume) : NULL, NULL)) < 0) { - fprintf(stderr, _("pa_stream_connect_playback() failed: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("pa_stream_connect_playback() failed: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } } else { 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))); + pa_log(_("pa_stream_connect_record() failed: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } } @@ -326,7 +459,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: - fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } @@ -337,42 +470,14 @@ fail: } -/* Connection draining complete */ -static void context_drain_complete(pa_context*c, void *userdata) { - pa_context_disconnect(c); -} - -/* Stream draining complete */ -static void stream_drain_complete(pa_stream*s, int success, void *userdata) { - - if (!success) { - fprintf(stderr, _("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - } - - if (verbose) - fprintf(stderr, _("Playback stream drained.\n")); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = NULL; - - if (!pa_context_drain(context, context_drain_complete, NULL)) - pa_context_disconnect(context); - else { - if (verbose) - fprintf(stderr, _("Draining connection to server.\n")); - } -} - /* New data on STDIN **/ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { size_t l, w = 0; ssize_t r; - assert(a == mainloop_api); - assert(e); - assert(stdio_event == e); + pa_assert(a == mainloop_api); + pa_assert(e); + pa_assert(stdio_event == e); if (buffer) { mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); @@ -387,23 +492,12 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even if ((r = read(fd, buffer, l)) <= 0) { if (r == 0) { if (verbose) - fprintf(stderr, _("Got EOF.\n")); - - if (stream) { - pa_operation *o; - - if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) { - fprintf(stderr, _("pa_stream_drain(): %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - return; - } + pa_log(_("Got EOF.\n")); - pa_operation_unref(o); - } else - quit(0); + start_drain(); } else { - fprintf(stderr, _("read() failed: %s\n"), strerror(errno)); + pa_log(_("read() failed: %s\n"), strerror(errno)); quit(1); } @@ -423,19 +517,19 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { ssize_t r; - assert(a == mainloop_api); - assert(e); - assert(stdio_event == e); + pa_assert(a == mainloop_api); + pa_assert(e); + pa_assert(stdio_event == e); if (!buffer) { mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); return; } - assert(buffer_length); + pa_assert(buffer_length); if ((r = write(fd, (uint8_t*) buffer+buffer_index, buffer_length)) <= 0) { - fprintf(stderr, _("write() failed: %s\n"), strerror(errno)); + pa_log(_("write() failed: %s\n"), strerror(errno)); quit(1); mainloop_api->io_free(stdio_event); @@ -456,7 +550,7 @@ static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_eve /* UNIX signal to quit recieved */ static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { if (verbose) - fprintf(stderr, _("Got signal, exiting.\n")); + pa_log(_("Got signal, exiting.\n")); quit(0); } @@ -465,17 +559,17 @@ static void stream_update_timing_callback(pa_stream *s, int success, void *userd pa_usec_t l, usec; int negative = 0; - assert(s); + pa_assert(s); if (!success || pa_stream_get_time(s, &usec) < 0 || pa_stream_get_latency(s, &l, &negative) < 0) { - fprintf(stderr, _("Failed to get latency: %s\n"), pa_strerror(pa_context_errno(context))); + pa_log(_("Failed to get latency: %s\n"), pa_strerror(pa_context_errno(context))); quit(1); return; } - fprintf(stderr, _("Time: %0.3f sec; Latency: %0.0f usec. \r"), + pa_log(_("Time: %0.3f sec; Latency: %0.0f usec. \r"), (float) usec / 1000000, (float) l * (negative?-1.0f:1.0f)); } @@ -495,7 +589,7 @@ static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struc if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) { pa_operation *o; if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL))) - fprintf(stderr, _("pa_stream_update_timing_info() failed: %s\n"), pa_strerror(pa_context_errno(context))); + pa_log(_("pa_stream_update_timing_info() failed: %s\n"), pa_strerror(pa_context_errno(context))); else pa_operation_unref(o); } @@ -509,34 +603,38 @@ static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struc static void help(const char *argv0) { printf(_("%s [options]\n\n" - " -h, --help Show this help\n" - " --version Show version\n\n" - " -r, --record Create a connection for recording\n" - " -p, --playback Create a connection for playback\n\n" - " -v, --verbose Enable verbose operations\n\n" - " -s, --server=SERVER The name of the server to connect to\n" - " -d, --device=DEVICE The name of the sink/source to connect to\n" - " -n, --client-name=NAME How to call this client on the server\n" - " --stream-name=NAME How to call this stream on the server\n" - " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" - " --rate=SAMPLERATE The sample rate in Hz (defaults to 44100)\n" - " --format=SAMPLEFORMAT The sample type, one of s16le, s16be, u8, float32le,\n" - " float32be, ulaw, alaw, s32le, s32be (defaults to s16ne)\n" - " --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo\n" - " (defaults to 2)\n" - " --channel-map=CHANNELMAP Channel map to use instead of the default\n" - " --fix-format Take the sample format from the sink the stream is\n" - " being connected to.\n" - " --fix-rate Take the sampling rate from the sink the stream is\n" - " being connected to.\n" - " --fix-channels Take the number of channels and the channel map\n" - " from the sink the stream is being connected to.\n" - " --no-remix Don't upmix or downmix channels.\n" - " --no-remap Map channels by index instead of name.\n" - " --latency=BYTES Request the specified latency in bytes.\n" - " --process-time=BYTES Request the specified process time per request in bytes.\n") - , - argv0); + " -h, --help Show this help\n" + " --version Show version\n\n" + " -r, --record Create a connection for recording\n" + " -p, --playback Create a connection for playback\n\n" + " -v, --verbose Enable verbose operations\n\n" + " -s, --server=SERVER The name of the server to connect to\n" + " -d, --device=DEVICE The name of the sink/source to connect to\n" + " -n, --client-name=NAME How to call this client on the server\n" + " --stream-name=NAME How to call this stream on the server\n" + " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" + " --rate=SAMPLERATE The sample rate in Hz (defaults to 44100)\n" + " --format=SAMPLEFORMAT The sample type, one of s16le, s16be, u8, float32le,\n" + " float32be, ulaw, alaw, s32le, s32be, s24le, s24be,\n" + " s24-32le, s24-32be (defaults to s16ne)\n" + " --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo\n" + " (defaults to 2)\n" + " --channel-map=CHANNELMAP Channel map to use instead of the default\n" + " --fix-format Take the sample format from the sink the stream is\n" + " being connected to.\n" + " --fix-rate Take the sampling rate from the sink the stream is\n" + " being connected to.\n" + " --fix-channels Take the number of channels and the channel map\n" + " from the sink the stream is being connected to.\n" + " --no-remix Don't upmix or downmix channels.\n" + " --no-remap Map channels by index instead of name.\n" + " --latency=BYTES Request the specified latency in bytes.\n" + " --process-time=BYTES Request the specified process time per request in bytes.\n" + " --property=PROPERTY=VALUE Set the specified property to the specified value.\n" + " --raw Record/play raw PCM data.\n" + " --file-format=FFORMAT Record/play formatted PCM data.\n" + " --list-file-formats List available file formats.\n") + , argv0); } enum { @@ -553,14 +651,19 @@ enum { ARG_NO_REMAP, ARG_NO_REMIX, ARG_LATENCY, - ARG_PROCESS_TIME + ARG_PROCESS_TIME, + ARG_RAW, + ARG_PROPERTY, + ARG_FILE_FORMAT, + ARG_LIST_FILE_FORMATS }; int main(int argc, char *argv[]) { pa_mainloop* m = NULL; - int ret = 1, r, c; + int ret = 1, c; char *bn, *server = NULL; pa_time_event *time_event = NULL; + const char *filename = NULL; static const struct option long_options[] = { {"record", 0, NULL, 'r'}, @@ -584,21 +687,33 @@ int main(int argc, char *argv[]) { {"no-remix", 0, NULL, ARG_NO_REMIX}, {"latency", 1, NULL, ARG_LATENCY}, {"process-time", 1, NULL, ARG_PROCESS_TIME}, + {"property", 1, NULL, ARG_PROPERTY}, + {"raw", 0, NULL, ARG_RAW}, + {"file-format", 2, NULL, ARG_FILE_FORMAT}, + {"list-file-formats", 0, NULL, ARG_LIST_FILE_FORMATS}, {NULL, 0, NULL, 0} }; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); - if (!(bn = strrchr(argv[0], '/'))) - bn = argv[0]; - else - bn++; + bn = pa_path_get_filename(argv[0]); - if (strstr(bn, "rec") || strstr(bn, "mon")) + if (strstr(bn, "play")) { + mode = PLAYBACK; + raw = FALSE; + } else if (strstr(bn, "record")) { mode = RECORD; - else if (strstr(bn, "cat") || strstr(bn, "play")) + raw = FALSE; + } else if (strstr(bn, "cat")) { mode = PLAYBACK; + raw = TRUE; + } if (strstr(bn, "rec") || strstr(bn, "mon")) { + mode = RECORD; + raw = TRUE; + } + + proplist = pa_proplist_new(); while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) { @@ -609,7 +724,12 @@ int main(int argc, char *argv[]) { goto quit; case ARG_VERSION: - printf(_("pacat %s\nCompiled with libpulse %s\nLinked with libpulse %s\n"), PACKAGE_VERSION, pa_get_headers_version(), pa_get_library_version()); + printf(_("pacat %s\n" + "Compiled with libpulse %s\n" + "Linked with libpulse %s\n"), + PACKAGE_VERSION, + pa_get_headers_version(), + pa_get_library_version()); ret = 0; goto quit; @@ -631,15 +751,36 @@ int main(int argc, char *argv[]) { server = pa_xstrdup(optarg); break; - case 'n': - pa_xfree(client_name); - client_name = pa_xstrdup(optarg); + case 'n': { + char *t; + + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t) < 0) { + + pa_log(_("Invalid client name '%s'\n"), t ? t : optarg); + pa_xfree(t); + goto quit; + } + + pa_xfree(t); break; + } + + case ARG_STREAM_NAME: { + char *t; + t = pa_locale_to_utf8(optarg); - case ARG_STREAM_NAME: - pa_xfree(stream_name); - stream_name = pa_xstrdup(optarg); + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t) < 0) { + + pa_log(_("Invalid stream name '%s'\n"), t ? t : optarg); + pa_xfree(t); + goto quit; + } + + pa_xfree(t); break; + } case 'v': verbose = 1; @@ -648,29 +789,32 @@ int main(int argc, char *argv[]) { case ARG_VOLUME: { int v = atoi(optarg); volume = v < 0 ? 0U : (pa_volume_t) v; - volume_is_set = 1; + volume_is_set = TRUE; break; } case ARG_CHANNELS: sample_spec.channels = (uint8_t) atoi(optarg); + sample_spec_set = TRUE; break; case ARG_SAMPLEFORMAT: sample_spec.format = pa_parse_sample_format(optarg); + sample_spec_set = TRUE; break; case ARG_SAMPLERATE: sample_spec.rate = (uint32_t) atoi(optarg); + sample_spec_set = TRUE; break; case ARG_CHANNELMAP: if (!pa_channel_map_parse(&channel_map, optarg)) { - fprintf(stderr, _("Invalid channel map '%s'\n"), optarg); + pa_log(_("Invalid channel map '%s'\n"), optarg); goto quit; } - channel_map_set = 1; + channel_map_set = TRUE; break; case ARG_FIX_CHANNELS: @@ -695,17 +839,54 @@ int main(int argc, char *argv[]) { case ARG_LATENCY: if (((latency = (size_t) atoi(optarg))) <= 0) { - fprintf(stderr, _("Invalid latency specification '%s'\n"), optarg); + pa_log(_("Invalid latency specification '%s'\n"), optarg); goto quit; } break; case ARG_PROCESS_TIME: if (((process_time = (size_t) atoi(optarg))) <= 0) { - fprintf(stderr, _("Invalid process time specification '%s'\n"), optarg); + pa_log(_("Invalid process time specification '%s'\n"), optarg); + goto quit; + } + break; + + case ARG_PROPERTY: { + char *t; + + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_setp(proplist, t) < 0) { + + pa_xfree(t); + pa_log(_("Invalid property '%s'\n"), optarg); goto quit; } + + pa_xfree(t); break; + } + + case ARG_RAW: + raw = TRUE; + break; + + case ARG_FILE_FORMAT: + raw = FALSE; + + if (optarg) { + if ((file_format = pa_sndfile_format_from_string(optarg)) < 0) { + pa_log(_("Unknown file format %s."), optarg); + goto quit; + } + } + + raw = FALSE; + break; + + case ARG_LIST_FILE_FORMATS: + pa_sndfile_dump_formats(); + ret = 0; + goto quit; default: goto quit; @@ -713,82 +894,168 @@ int main(int argc, char *argv[]) { } if (!pa_sample_spec_valid(&sample_spec)) { - fprintf(stderr, _("Invalid sample specification\n")); + pa_log(_("Invalid sample specification\n")); goto quit; } - if (channel_map_set && pa_channel_map_compatible(&channel_map, &sample_spec)) { - fprintf(stderr, _("Channel map doesn't match sample specification\n")); - goto quit; - } + if (optind+1 == argc) { + int fd; - if (verbose) { - char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; - pa_sample_spec_snprint(t, sizeof(t), &sample_spec); - fprintf(stderr, _("Opening a %s stream with sample specification '%s'.\n"), mode == RECORD ? _("recording") : _("playback"), t); + filename = argv[optind]; + + if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { + pa_log(_("open(): %s\n"), strerror(errno)); + goto quit; + } + + if (dup2(fd, mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO) < 0) { + pa_log(_("dup2(): %s\n"), strerror(errno)); + goto quit; + } + + pa_close(fd); + + } else if (optind+1 <= argc) { + pa_log(_("Too many arguments.\n")); + goto quit; } - if (!(optind >= argc)) { - if (optind+1 == argc) { - int fd; + if (!raw) { + SF_INFO sfi; + pa_zero(sfi); - if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { - fprintf(stderr, _("open(): %s\n"), strerror(errno)); + if (mode == RECORD) { + /* This might patch up the sample spec */ + if (pa_sndfile_write_sample_spec(&sfi, &sample_spec) < 0) { + pa_log(_("Failed to generate sample specification for file.\n")); goto quit; } - if (dup2(fd, mode == PLAYBACK ? 0 : 1) < 0) { - fprintf(stderr, _("dup2(): %s\n"), strerror(errno)); + /* Transparently upgrade classic .wav to wavex for multichannel audio */ + if (file_format <= 0) { + if ((sample_spec.channels == 2 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_LEFT && + channel_map.map[1] == PA_CHANNEL_POSITION_RIGHT))) || + (sample_spec.channels == 1 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_MONO)))) + file_format = SF_FORMAT_WAV; + else + file_format = SF_FORMAT_WAVEX; + } + + sfi.format |= file_format; + } + + if (!(sndfile = sf_open_fd(mode == RECORD ? STDOUT_FILENO : STDIN_FILENO, + mode == RECORD ? SFM_WRITE : SFM_READ, + &sfi, 0))) { + pa_log(_("Failed to open audio file.\n")); + goto quit; + } + + if (mode == PLAYBACK) { + if (sample_spec_set) + pa_log(_("Warning: specified sample specification will be overwritten with specification from file.\n")); + + if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { + pa_log(_("Failed to determine sample specification from file.\n")); goto quit; } + sample_spec_set = TRUE; + + if (!channel_map_set) { + /* Allow the user to overwrite the channel map on the command line */ + if (pa_sndfile_read_channel_map(sndfile, &channel_map) < 0) { + if (sample_spec.channels > 2) + pa_log(_("Warning: Failed to determine channel map from file.\n")); + } else + channel_map_set = TRUE; + } + } + } - close(fd); + if (!channel_map_set) + pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); - if (!stream_name) - stream_name = pa_xstrdup(argv[optind]); + if (!pa_channel_map_compatible(&channel_map, &sample_spec)) { + pa_log(_("Channel map doesn't match sample specification\n")); + goto quit; + } - } else { - fprintf(stderr, _("Too many arguments.\n")); - goto quit; + if (!raw) { + pa_proplist *sfp; + + if (mode == PLAYBACK) + readf_function = pa_sndfile_readf_function(&sample_spec); + else { + if (pa_sndfile_write_channel_map(sndfile, &channel_map) < 0) + pa_log(_("Warning: failed to write channel map to file.\n")); + + writef_function = pa_sndfile_writef_function(&sample_spec); } + + /* Fill in libsndfile prop list data */ + sfp = pa_proplist_new(); + pa_sndfile_init_proplist(sndfile, sfp); + pa_proplist_update(proplist, PA_UPDATE_MERGE, sfp); + pa_proplist_free(sfp); } - if (!client_name) - client_name = pa_xstrdup(bn); + if (verbose) { + char tss[PA_SAMPLE_SPEC_SNPRINT_MAX], tcm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_log(_("Opening a %s stream with sample specification '%s' and channel map '%s'.\n"), + mode == RECORD ? _("recording") : _("playback"), + pa_sample_spec_snprint(tss, sizeof(tss), &sample_spec), + pa_channel_map_snprint(tcm, sizeof(tcm), &channel_map)); + } - if (!stream_name) - stream_name = pa_xstrdup(client_name); + /* Fill in client name if none was set */ + if (!pa_proplist_contains(proplist, PA_PROP_APPLICATION_NAME)) { + char *t; + + if ((t = pa_locale_to_utf8(bn))) { + pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t); + pa_xfree(t); + } + } + + /* Fill in media name if none was set */ + if (!pa_proplist_contains(proplist, PA_PROP_MEDIA_NAME)) { + const char *t; + + if ((t = filename) || + (t = pa_proplist_gets(proplist, PA_PROP_APPLICATION_NAME))) + pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t); + } /* Set up a new main loop */ if (!(m = pa_mainloop_new())) { - fprintf(stderr, _("pa_mainloop_new() failed.\n")); + pa_log(_("pa_mainloop_new() failed.\n")); goto quit; } mainloop_api = pa_mainloop_get_api(m); - r = pa_signal_init(mainloop_api); - assert(r == 0); + pa_assert_se(pa_signal_init(mainloop_api) == 0); pa_signal_new(SIGINT, exit_signal_callback, NULL); pa_signal_new(SIGTERM, exit_signal_callback, NULL); #ifdef SIGUSR1 pa_signal_new(SIGUSR1, sigusr1_signal_callback, NULL); #endif -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif - - if (!(stdio_event = mainloop_api->io_new(mainloop_api, - mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, - mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT, - mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) { - fprintf(stderr, _("io_new() failed.\n")); - goto quit; + pa_disable_sigpipe(); + + if (raw) { + if (!(stdio_event = mainloop_api->io_new(mainloop_api, + mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, + mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT, + mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) { + pa_log(_("io_new() failed.\n")); + goto quit; + } } /* Create a new connection context */ - if (!(context = pa_context_new(mainloop_api, client_name))) { - fprintf(stderr, _("pa_context_new() failed.\n")); + if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { + pa_log(_("pa_context_new() failed.\n")); goto quit; } @@ -796,7 +1063,7 @@ int main(int argc, char *argv[]) { /* Connect the context */ if (pa_context_connect(context, server, 0, NULL) < 0) { - fprintf(stderr, _("pa_context_connect() failed: %s\n"), pa_strerror(pa_context_errno(context))); + pa_log(_("pa_context_connect() failed: %s\n"), pa_strerror(pa_context_errno(context))); goto quit; } @@ -807,14 +1074,14 @@ int main(int argc, char *argv[]) { 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")); + pa_log(_("time_new() failed.\n")); goto quit; } } /* Run the main loop */ if (pa_mainloop_run(m, &ret) < 0) { - fprintf(stderr, _("pa_mainloop_run() failed.\n")); + pa_log(_("pa_mainloop_run() failed.\n")); goto quit; } @@ -826,12 +1093,12 @@ quit: pa_context_unref(context); if (stdio_event) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->io_free(stdio_event); } if (time_event) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->time_free(time_event); } @@ -844,8 +1111,12 @@ quit: pa_xfree(server); pa_xfree(device); - pa_xfree(client_name); - pa_xfree(stream_name); + + if (sndfile) + sf_close(sndfile); + + if (proplist) + pa_proplist_free(proplist); return ret; } diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c index d94f2665..ac60a0bc 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -38,11 +38,13 @@ #include <pulse/xmalloc.h> #include <pulse/i18n.h> +#include <pulsecore/macro.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> #include <pulsecore/pid.h> int main(int argc, char*argv[]) { + pid_t pid ; int fd = -1; int ret = 1, i; @@ -56,7 +58,7 @@ int main(int argc, char*argv[]) { bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) { - pa_log("No PulseAudio daemon running, or not running as session daemon."); + pa_log(_("No PulseAudio daemon running, or not running as session daemon.")); goto fail; } @@ -65,7 +67,7 @@ int main(int argc, char*argv[]) { goto fail; } - memset(&sa, 0, sizeof(sa)); + pa_zero(sa); sa.sun_family = AF_UNIX; if (!(cli = pa_runtime_path("cli"))) @@ -147,9 +149,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(0, &ifds)) { ssize_t r; - assert(!ibuf_length); + pa_assert(!ibuf_length); - if ((r = read(0, ibuf, sizeof(ibuf))) <= 0) { + if ((r = pa_read(0, ibuf, sizeof(ibuf), NULL)) <= 0) { if (r < 0) { pa_log(_("read(): %s"), strerror(errno)); goto fail; @@ -164,9 +166,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(fd, &ifds)) { ssize_t r; - assert(!obuf_length); + pa_assert(!obuf_length); - if ((r = read(fd, obuf, sizeof(obuf))) <= 0) { + if ((r = pa_read(fd, obuf, sizeof(obuf), NULL)) <= 0) { if (r < 0) { pa_log(_("read(): %s"), strerror(errno)); goto fail; @@ -181,9 +183,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(1, &ofds)) { ssize_t r; - assert(obuf_length); + pa_assert(obuf_length); - if ((r = write(1, obuf + obuf_index, obuf_length)) < 0) { + if ((r = pa_write(1, obuf + obuf_index, obuf_length, NULL)) < 0) { pa_log(_("write(): %s"), strerror(errno)); goto fail; } @@ -195,9 +197,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(fd, &ofds)) { ssize_t r; - assert(ibuf_length); + pa_assert(ibuf_length); - if ((r = write(fd, ibuf + ibuf_index, ibuf_length)) < 0) { + if ((r = pa_write(fd, ibuf + ibuf_index, ibuf_length, NULL)) < 0) { pa_log(_("write(): %s"), strerror(errno)); goto fail; } @@ -207,14 +209,14 @@ int main(int argc, char*argv[]) { } if (ibuf_length <= 0 && ibuf_eof && !ibuf_closed) { - close(0); + pa_close(0); shutdown(fd, SHUT_WR); ibuf_closed = TRUE; } if (obuf_length <= 0 && obuf_eof && !obuf_closed) { shutdown(fd, SHUT_RD); - close(1); + pa_close(1); obuf_closed = TRUE; } } diff --git a/src/utils/pactl.c b/src/utils/pactl.c index de1c6d3d..6608c01e 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -38,9 +38,13 @@ #include <pulse/i18n.h> #include <pulse/pulseaudio.h> + +#include <pulsecore/macro.h> #include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/sndfile-util.h> -#define BUFSIZE 1024 +#define BUFSIZE (16*1024) static pa_context *context = NULL; static pa_mainloop_api *mainloop_api = NULL; @@ -48,16 +52,19 @@ static pa_mainloop_api *mainloop_api = NULL; static char *device = NULL, *sample_name = NULL, *sink_name = NULL, *source_name = NULL, *module_name = NULL, *module_args = NULL, *card_name = NULL, *profile_name = 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 pa_bool_t suspend; + +static pa_proplist *proplist = NULL; static SNDFILE *sndfile = NULL; static pa_stream *sample_stream = NULL; static pa_sample_spec sample_spec; +static pa_channel_map channel_map; static size_t sample_length = 0; static int actions = 1; -static int nl = 0; +static pa_bool_t nl = FALSE; static enum { NONE, @@ -77,11 +84,10 @@ static enum { } action = NONE; static void quit(int ret) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->quit(mainloop_api, ret); } - static void context_drain_complete(pa_context *c, void *userdata) { pa_context_disconnect(c); } @@ -94,9 +100,8 @@ static void drain(void) { pa_operation_unref(o); } - static void complete_action(void) { - assert(actions > 0); + pa_assert(actions > 0); if (!(--actions)) drain(); @@ -105,7 +110,7 @@ static void complete_action(void) { static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) { char s[128]; if (!i) { - fprintf(stderr, _("Failed to get statistics: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get statistics: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -126,7 +131,7 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; if (!i) { - fprintf(stderr, _("Failed to get server information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get server information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -175,7 +180,7 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get sink information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sink information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -185,11 +190,11 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; printf(_("Sink #%u\n" "\tState: %s\n" @@ -255,7 +260,7 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get source information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get source information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -265,11 +270,11 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; printf(_("Source #%u\n" "\tState: %s\n" @@ -321,7 +326,7 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get module information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get module information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -331,13 +336,13 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->n_used); + pa_snprintf(t, sizeof(t), "%u", i->n_used); printf(_("Module #%u\n" "\tName: %s\n" @@ -358,7 +363,7 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get client information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get client information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -368,13 +373,13 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); printf(_("Client #%u\n" "\tDriver: %s\n" @@ -393,7 +398,7 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_ char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get card information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get card information: %s\n"), pa_strerror(pa_context_errno(c))); complete_action(); return; } @@ -403,13 +408,13 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_ return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); printf(_("Card #%u\n" "\tName: %s\n" @@ -442,7 +447,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get sink input information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sink input information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -452,14 +457,14 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); - snprintf(k, sizeof(k), "%u", i->client); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(k, sizeof(k), "%u", i->client); printf(_("Sink Input #%u\n" "\tDriver: %s\n" @@ -500,7 +505,7 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get source output information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get source output information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -510,15 +515,15 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); - snprintf(k, sizeof(k), "%u", i->client); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(k, sizeof(k), "%u", i->client); printf(_("Source Output #%u\n" "\tDriver: %s\n" @@ -551,7 +556,7 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get sample information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sample information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -561,11 +566,11 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; pa_bytes_snprint(t, sizeof(t), i->bytes); @@ -599,7 +604,7 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int static void simple_callback(pa_context *c, int success, void *userdata) { if (!success) { - fprintf(stderr, _("Failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -609,7 +614,7 @@ static void simple_callback(pa_context *c, int success, void *userdata) { 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))); + pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -620,7 +625,7 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) { } static void stream_state_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); switch (pa_stream_get_state(s)) { case PA_STREAM_CREATING: @@ -633,7 +638,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) { case PA_STREAM_FAILED: default: - fprintf(stderr, _("Failed to upload sample: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_log(_("Failed to upload sample: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); quit(1); } } @@ -641,16 +646,16 @@ static void stream_state_callback(pa_stream *s, void *userdata) { static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { sf_count_t l; float *d; - assert(s && length && sndfile); + pa_assert(s && length && sndfile); d = pa_xmalloc(length); - assert(sample_length >= length); + pa_assert(sample_length >= length); l = (sf_count_t) (length/pa_frame_size(&sample_spec)); if ((sf_readf_float(sndfile, d, l)) != l) { pa_xfree(d); - fprintf(stderr, _("Premature end of file\n")); + pa_log(_("Premature end of file\n")); quit(1); return; } @@ -666,7 +671,7 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { } static void context_state_callback(pa_context *c, void *userdata) { - assert(c); + pa_assert(c); switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: @@ -691,7 +696,7 @@ 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_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); @@ -749,7 +754,7 @@ static void context_state_callback(pa_context *c, void *userdata) { break; default: - assert(0); + pa_assert_not_reached(); } break; @@ -759,13 +764,13 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: - fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); } } static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { - fprintf(stderr, _("Got SIGINT, exiting.\n")); + pa_log(_("Got SIGINT, exiting.\n")); quit(0); } @@ -791,13 +796,14 @@ static void help(const char *argv0) { argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0); } -enum { ARG_VERSION = 256 }; +enum { + ARG_VERSION = 256 +}; int main(int argc, char *argv[]) { pa_mainloop* m = NULL; - char tmp[PATH_MAX]; - int ret = 1, r, c; - char *server = NULL, *client_name = NULL, *bn; + int ret = 1, c; + char *server = NULL, *bn; static const struct option long_options[] = { {"server", 1, NULL, 's'}, @@ -810,10 +816,9 @@ int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); - if (!(bn = strrchr(argv[0], '/'))) - bn = argv[0]; - else - bn++; + bn = pa_path_get_filename(argv[0]); + + proplist = pa_proplist_new(); while ((c = getopt_long(argc, argv, "s:n:h", long_options, NULL)) != -1) { switch (c) { @@ -837,66 +842,74 @@ int main(int argc, char *argv[]) { server = pa_xstrdup(optarg); break; - case 'n': - pa_xfree(client_name); - client_name = pa_xstrdup(optarg); + case 'n': { + char *t; + + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t) < 0) { + + pa_log(_("Invalid client name '%s'\n"), t ? t : optarg); + pa_xfree(t); + goto quit; + } + + pa_xfree(t); break; + } default: goto quit; } } - if (!client_name) - client_name = pa_xstrdup(bn); - if (optind < argc) { - if (!strcmp(argv[optind], "stat")) + if (pa_streq(argv[optind], "stat")) action = STAT; - else if (!strcmp(argv[optind], "exit")) + else if (pa_streq(argv[optind], "exit")) action = EXIT; - else if (!strcmp(argv[optind], "list")) + else if (pa_streq(argv[optind], "list")) action = LIST; - else if (!strcmp(argv[optind], "upload-sample")) { - struct SF_INFO sfinfo; + else if (pa_streq(argv[optind], "upload-sample")) { + struct SF_INFO sfi; action = UPLOAD_SAMPLE; if (optind+1 >= argc) { - fprintf(stderr, _("Please specify a sample file to load\n")); + pa_log(_("Please specify a sample file to load\n")); goto quit; } if (optind+2 < argc) sample_name = pa_xstrdup(argv[optind+2]); else { - char *f = strrchr(argv[optind+1], '/'); - size_t n; - if (f) - f++; - else - f = argv[optind]; - - n = strcspn(f, "."); - strncpy(tmp, f, n); - tmp[n] = 0; - sample_name = pa_xstrdup(tmp); + char *f = pa_path_get_filename(argv[optind+1]); + sample_name = pa_xstrndup(f, strcspn(f, ".")); } - memset(&sfinfo, 0, sizeof(sfinfo)); - if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfinfo))) { - fprintf(stderr, _("Failed to open sound file.\n")); + pa_zero(sfi); + if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfi))) { + pa_log(_("Failed to open sound file.\n")); goto quit; } + if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { + pa_log(_("Failed to determine sample specification from file.\n")); + goto quit; + } sample_spec.format = PA_SAMPLE_FLOAT32; - sample_spec.rate = (uint32_t) sfinfo.samplerate; - sample_spec.channels = (uint8_t) sfinfo.channels; - sample_length = (size_t)sfinfo.frames*pa_frame_size(&sample_spec); - } else if (!strcmp(argv[optind], "play-sample")) { + if (pa_sndfile_read_channel_map(sndfile, &channel_map) < 0) { + if (sample_spec.channels > 2) + pa_log(_("Warning: Failed to determine sample specification from file.\n")); + pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); + } + + pa_assert(pa_channel_map_compatible(&channel_map, &sample_spec)); + sample_length = (size_t) sfi.frames*pa_frame_size(&sample_spec); + + } else if (pa_streq(argv[optind], "play-sample")) { action = PLAY_SAMPLE; if (argc != optind+2 && argc != optind+3) { - fprintf(stderr, _("You have to specify a sample name to play\n")); + pa_log(_("You have to specify a sample name to play\n")); goto quit; } @@ -905,33 +918,36 @@ int main(int argc, char *argv[]) { if (optind+2 < argc) device = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "remove-sample")) { + } else if (pa_streq(argv[optind], "remove-sample")) { action = REMOVE_SAMPLE; if (argc != optind+2) { - fprintf(stderr, _("You have to specify a sample name to remove\n")); + pa_log(_("You have to specify a sample name to remove\n")); goto quit; } sample_name = pa_xstrdup(argv[optind+1]); - } else if (!strcmp(argv[optind], "move-sink-input")) { + + } else if (pa_streq(argv[optind], "move-sink-input")) { action = MOVE_SINK_INPUT; if (argc != optind+3) { - fprintf(stderr, _("You have to specify a sink input index and a sink\n")); + pa_log(_("You have to specify a sink input index and a sink\n")); goto quit; } sink_input_idx = (uint32_t) atoi(argv[optind+1]); sink_name = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "move-source-output")) { + + } else if (pa_streq(argv[optind], "move-source-output")) { action = MOVE_SOURCE_OUTPUT; if (argc != optind+3) { - fprintf(stderr, _("You have to specify a source output index and a source\n")); + pa_log(_("You have to specify a source output index and a source\n")); goto quit; } source_output_idx = (uint32_t) atoi(argv[optind+1]); source_name = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "load-module")) { + + } else if (pa_streq(argv[optind], "load-module")) { int i; size_t n = 0; char *p; @@ -939,7 +955,7 @@ int main(int argc, char *argv[]) { action = LOAD_MODULE; if (argc <= optind+1) { - fprintf(stderr, _("You have to specify a module name and arguments.\n")); + pa_log(_("You have to specify a module name and arguments.\n")); goto quit; } @@ -955,21 +971,21 @@ int main(int argc, char *argv[]) { p += sprintf(p, "%s%s", p == module_args ? "" : " ", argv[i]); } - } else if (!strcmp(argv[optind], "unload-module")) { + } else if (pa_streq(argv[optind], "unload-module")) { action = UNLOAD_MODULE; if (argc != optind+2) { - fprintf(stderr, _("You have to specify a module index\n")); + pa_log(_("You have to specify a module index\n")); goto quit; } module_index = (uint32_t) atoi(argv[optind+1]); - } else if (!strcmp(argv[optind], "suspend-sink")) { + } else if (pa_streq(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 a boolean value.\n")); + pa_log(_("You may not specify more than one sink. You have to specify a boolean value.\n")); goto quit; } @@ -978,11 +994,11 @@ int main(int argc, char *argv[]) { if (argc > optind+2) sink_name = pa_xstrdup(argv[optind+1]); - } else if (!strcmp(argv[optind], "suspend-source")) { + } else if (pa_streq(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 a boolean value.\n")); + pa_log(_("You may not specify more than one source. You have to specify a boolean value.\n")); goto quit; } @@ -990,18 +1006,18 @@ int main(int argc, char *argv[]) { if (argc > optind+2) source_name = pa_xstrdup(argv[optind+1]); - } else if (!strcmp(argv[optind], "set-card-profile")) { + } else if (pa_streq(argv[optind], "set-card-profile")) { action = SET_CARD_PROFILE; if (argc != optind+3) { - fprintf(stderr, _("You have to specify a card name/index and a profile name\n")); + pa_log(_("You have to specify a card name/index and a profile name\n")); goto quit; } card_name = pa_xstrdup(argv[optind+1]); profile_name = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "help")) { + } else if (pa_streq(argv[optind], "help")) { help(bn); ret = 0; goto quit; @@ -1009,37 +1025,35 @@ int main(int argc, char *argv[]) { } if (action == NONE) { - fprintf(stderr, _("No valid command specified.\n")); + pa_log(_("No valid command specified.\n")); goto quit; } if (!(m = pa_mainloop_new())) { - fprintf(stderr, _("pa_mainloop_new() failed.\n")); + pa_log(_("pa_mainloop_new() failed.\n")); goto quit; } mainloop_api = pa_mainloop_get_api(m); - r = pa_signal_init(mainloop_api); - assert(r == 0); + pa_assert_se(pa_signal_init(mainloop_api) == 0); pa_signal_new(SIGINT, exit_signal_callback, NULL); -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif + pa_signal_new(SIGTERM, exit_signal_callback, NULL); + pa_disable_sigpipe(); - if (!(context = pa_context_new(mainloop_api, client_name))) { - fprintf(stderr, _("pa_context_new() failed.\n")); + if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { + pa_log(_("pa_context_new() failed.\n")); goto quit; } pa_context_set_state_callback(context, context_state_callback, NULL); if (pa_context_connect(context, server, 0, NULL) < 0) { - fprintf(stderr, _("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); + pa_log(_("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); goto quit; } if (pa_mainloop_run(m, &ret) < 0) { - fprintf(stderr, _("pa_mainloop_run() failed.\n")); + pa_log(_("pa_mainloop_run() failed.\n")); goto quit; } @@ -1055,18 +1069,20 @@ quit: pa_mainloop_free(m); } - if (sndfile) - sf_close(sndfile); - pa_xfree(server); pa_xfree(device); pa_xfree(sample_name); pa_xfree(sink_name); pa_xfree(source_name); pa_xfree(module_args); - pa_xfree(client_name); pa_xfree(card_name); pa_xfree(profile_name); + if (sndfile) + sf_close(sndfile); + + if (proplist) + pa_proplist_free(proplist); + return ret; } diff --git a/src/utils/paplay.c b/src/utils/paplay.c deleted file mode 100644 index f6ba6f6d..00000000 --- a/src/utils/paplay.c +++ /dev/null @@ -1,435 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2.1 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <signal.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> -#include <getopt.h> -#include <locale.h> - -#include <sndfile.h> - -#include <pulse/pulseaudio.h> -#include <pulse/i18n.h> - -static pa_context *context = NULL; -static pa_stream *stream = NULL; -static pa_mainloop_api *mainloop_api = NULL; - -static char *stream_name = NULL, *client_name = NULL, *device = NULL; - -static int verbose = 0; -static pa_volume_t volume = PA_VOLUME_NORM; - -static SNDFILE* sndfile = NULL; - -static pa_sample_spec sample_spec = { 0, 0, 0 }; -static pa_channel_map channel_map; -static int channel_map_set = 0; - -static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL; - -/* A shortcut for terminating the application */ -static void quit(int ret) { - assert(mainloop_api); - mainloop_api->quit(mainloop_api, ret); -} - -/* Connection draining complete */ -static void context_drain_complete(pa_context*c, void *userdata) { - pa_context_disconnect(c); -} - -/* Stream draining complete */ -static void stream_drain_complete(pa_stream*s, int success, void *userdata) { - pa_operation *o; - - if (!success) { - fprintf(stderr, _("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - } - - if (verbose) - fprintf(stderr, _("Playback stream drained.\n")); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = NULL; - - if (!(o = pa_context_drain(context, context_drain_complete, NULL))) - pa_context_disconnect(context); - else { - pa_operation_unref(o); - - if (verbose) - fprintf(stderr, _("Draining connection to server.\n")); - } -} - -/* This is called whenever new data may be written to the stream */ -static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { - sf_count_t bytes; - void *data; - assert(s && length); - - if (!sndfile) - return; - - data = pa_xmalloc(length); - - if (readf_function) { - size_t k = pa_frame_size(&sample_spec); - - if ((bytes = readf_function(sndfile, data, (sf_count_t) (length/k))) > 0) - bytes *= (sf_count_t) k; - - } else - bytes = sf_read_raw(sndfile, data, (sf_count_t) length); - - if (bytes > 0) - pa_stream_write(s, data, (size_t) bytes, pa_xfree, 0, PA_SEEK_RELATIVE); - else - pa_xfree(data); - - if (bytes < (sf_count_t) length) { - sf_close(sndfile); - sndfile = NULL; - pa_operation_unref(pa_stream_drain(s, stream_drain_complete, NULL)); - } -} - -/* This routine is called whenever the stream state changes */ -static void stream_state_callback(pa_stream *s, void *userdata) { - assert(s); - - switch (pa_stream_get_state(s)) { - case PA_STREAM_CREATING: - case PA_STREAM_TERMINATED: - break; - - case PA_STREAM_READY: - if (verbose) - fprintf(stderr, _("Stream successfully created\n")); - break; - - case PA_STREAM_FAILED: - default: - fprintf(stderr, _("Stream errror: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - quit(1); - } -} - -/* This is called whenever the context status changes */ -static void context_state_callback(pa_context *c, void *userdata) { - assert(c); - - switch (pa_context_get_state(c)) { - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - - case PA_CONTEXT_READY: { - pa_cvolume cv; - - assert(c && !stream); - - if (verbose) - fprintf(stderr, _("Connection established.\n")); - - stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL); - assert(stream); - - pa_stream_set_state_callback(stream, stream_state_callback, NULL); - pa_stream_set_write_callback(stream, stream_write_callback, NULL); - pa_stream_connect_playback(stream, device, NULL, 0, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL); - - break; - } - - case PA_CONTEXT_TERMINATED: - quit(0); - break; - - case PA_CONTEXT_FAILED: - default: - fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); - quit(1); - } -} - -/* UNIX signal to quit recieved */ -static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { - if (verbose) - fprintf(stderr, _("Got SIGINT, exiting.\n")); - quit(0); - -} - -static void help(const char *argv0) { - - printf(_("%s [options] [FILE]\n\n" - " -h, --help Show this help\n" - " --version Show version\n\n" - " -v, --verbose Enable verbose operation\n\n" - " -s, --server=SERVER The name of the server to connect to\n" - " -d, --device=DEVICE The name of the sink to connect to\n" - " -n, --client-name=NAME How to call this client on the server\n" - " --stream-name=NAME How to call this stream on the server\n" - " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" - " --channel-map=CHANNELMAP Set the channel map to the use\n"), - argv0); -} - -enum { - ARG_VERSION = 256, - ARG_STREAM_NAME, - ARG_VOLUME, - ARG_CHANNELMAP -}; - -int main(int argc, char *argv[]) { - pa_mainloop* m = NULL; - int ret = 1, r, c; - char *bn, *server = NULL; - const char *filename; - SF_INFO sfinfo; - - static const struct option long_options[] = { - {"device", 1, NULL, 'd'}, - {"server", 1, NULL, 's'}, - {"client-name", 1, NULL, 'n'}, - {"stream-name", 1, NULL, ARG_STREAM_NAME}, - {"version", 0, NULL, ARG_VERSION}, - {"help", 0, NULL, 'h'}, - {"verbose", 0, NULL, 'v'}, - {"volume", 1, NULL, ARG_VOLUME}, - {"channel-map", 1, NULL, ARG_CHANNELMAP}, - {NULL, 0, NULL, 0} - }; - - setlocale(LC_ALL, ""); - bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); - - if (!(bn = strrchr(argv[0], '/'))) - bn = argv[0]; - else - bn++; - - while ((c = getopt_long(argc, argv, "d:s:n:hv", long_options, NULL)) != -1) { - - switch (c) { - case 'h' : - help(bn); - ret = 0; - goto quit; - - case ARG_VERSION: - printf(_("paplay %s\nCompiled with libpulse %s\n" - "Linked with libpulse %s\n"), PACKAGE_VERSION, pa_get_headers_version(), pa_get_library_version()); - ret = 0; - goto quit; - - case 'd': - pa_xfree(device); - device = pa_xstrdup(optarg); - break; - - case 's': - pa_xfree(server); - server = pa_xstrdup(optarg); - break; - - case 'n': - pa_xfree(client_name); - client_name = pa_xstrdup(optarg); - break; - - case ARG_STREAM_NAME: - pa_xfree(stream_name); - stream_name = pa_xstrdup(optarg); - break; - - case 'v': - verbose = 1; - break; - - case ARG_VOLUME: { - int v = atoi(optarg); - volume = v < 0 ? 0U : (pa_volume_t) v; - break; - } - - case ARG_CHANNELMAP: - if (!pa_channel_map_parse(&channel_map, optarg)) { - fprintf(stderr, _("Invalid channel map\n")); - goto quit; - } - - channel_map_set = 1; - break; - - default: - goto quit; - } - } - - filename = optind < argc ? argv[optind] : "STDIN"; - - memset(&sfinfo, 0, sizeof(sfinfo)); - - if (optind < argc) - sndfile = sf_open(filename, SFM_READ, &sfinfo); - else - sndfile = sf_open_fd(STDIN_FILENO, SFM_READ, &sfinfo, 0); - - if (!sndfile) { - fprintf(stderr, _("Failed to open file '%s'\n"), filename); - goto quit; - } - - sample_spec.rate = (uint32_t) sfinfo.samplerate; - sample_spec.channels = (uint8_t) sfinfo.channels; - - readf_function = NULL; - - switch (sfinfo.format & 0xFF) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - sample_spec.format = PA_SAMPLE_S16NE; - readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_short; - break; - - case SF_FORMAT_ULAW: - sample_spec.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - sample_spec.format = PA_SAMPLE_ALAW; - break; - - case SF_FORMAT_FLOAT: - case SF_FORMAT_DOUBLE: - default: - sample_spec.format = PA_SAMPLE_FLOAT32NE; - readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_float; - break; - } - - assert(pa_sample_spec_valid(&sample_spec)); - - if (channel_map_set && channel_map.channels != sample_spec.channels) { - fprintf(stderr, _("Channel map doesn't match file.\n")); - goto quit; - } - - if (!client_name) { - client_name = pa_locale_to_utf8(bn); - if (!client_name) - client_name = pa_utf8_filter(bn); - } - - if (!stream_name) { - const char *n; - - n = sf_get_string(sndfile, SF_STR_TITLE); - - if (!n) - n = filename; - - stream_name = pa_locale_to_utf8(n); - if (!stream_name) - stream_name = pa_utf8_filter(n); - } - - if (verbose) { - char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; - pa_sample_spec_snprint(t, sizeof(t), &sample_spec); - fprintf(stderr, _("Using sample spec '%s'\n"), t); - } - - /* Set up a new main loop */ - if (!(m = pa_mainloop_new())) { - fprintf(stderr, _("pa_mainloop_new() failed.\n")); - goto quit; - } - - mainloop_api = pa_mainloop_get_api(m); - - r = pa_signal_init(mainloop_api); - assert(r == 0); - pa_signal_new(SIGINT, exit_signal_callback, NULL); -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif - - /* Create a new connection context */ - if (!(context = pa_context_new(mainloop_api, client_name))) { - fprintf(stderr, _("pa_context_new() failed.\n")); - goto quit; - } - - pa_context_set_state_callback(context, context_state_callback, NULL); - - /* Connect the context */ - if (pa_context_connect(context, server, 0, NULL) < 0) { - fprintf(stderr, _("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); - goto quit; - } - - /* Run the main loop */ - if (pa_mainloop_run(m, &ret) < 0) { - fprintf(stderr, _("pa_mainloop_run() failed.\n")); - goto quit; - } - -quit: - if (stream) - pa_stream_unref(stream); - - if (context) - pa_context_unref(context); - - if (m) { - pa_signal_done(); - pa_mainloop_free(m); - } - - pa_xfree(server); - pa_xfree(device); - pa_xfree(client_name); - pa_xfree(stream_name); - - if (sndfile) - sf_close(sndfile); - - return ret; -} diff --git a/src/utils/pasuspender.c b/src/utils/pasuspender.c index b4bccd56..c327ee41 100644 --- a/src/utils/pasuspender.c +++ b/src/utils/pasuspender.c @@ -235,10 +235,7 @@ int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); - if (!(bn = strrchr(argv[0], '/'))) - bn = argv[0]; - else - bn++; + bn = pa_path_get_filename(argv[0]); while ((c = getopt_long(argc, argv, "s:h", long_options, NULL)) != -1) { switch (c) { |