Merge branch 'master' of git://0pointer.de/pulseaudio

This commit is contained in:
Daniel Mack 2009-09-14 16:25:35 +08:00
commit bebaa49165
231 changed files with 35120 additions and 22532 deletions

View file

@ -47,9 +47,9 @@ case $(uname) in
esac
if [ -f .git/hooks/pre-commit.sample -a ! -f .git/hooks/pre-commit ] ; then
echo "Activating pre-commit hook."
cp -pv .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod -v +x .git/hooks/pre-commit
cp -p .git/hooks/pre-commit.sample .git/hooks/pre-commit && \
chmod +x .git/hooks/pre-commit && \
echo "Activated pre-commit hook."
fi
if [ -f .tarball-version ]; then
@ -94,7 +94,7 @@ else
run_versioned automake "$VERSION" --copy --foreign --add-missing
if test "x$NOCONFIGURE" = "x"; then
CFLAGS="-g -O0" ./configure --sysconfdir=/etc --localstatedir=/var --enable-force-preopen "$@"
CFLAGS="$CFLAGS -g -O0" ./configure --sysconfdir=/etc --localstatedir=/var --enable-force-preopen "$@"
make clean
fi
fi

View file

@ -45,11 +45,11 @@ AC_SUBST(PA_PROTOCOL_VERSION, 16)
# The stable ABI for client applications, for the version info x:y:z
# always will hold y=z
AC_SUBST(LIBPULSE_VERSION_INFO, [8:0:8])
AC_SUBST(LIBPULSE_VERSION_INFO, [10:0:10])
# A simplified, synchronous, ABI-stable interface for client
# applications, for the version info x:y:z always will hold y=z
AC_SUBST(LIBPULSE_SIMPLE_VERSION_INFO, [0:2:0])
AC_SUBST(LIBPULSE_SIMPLE_VERSION_INFO, [0:3:0])
# The ABI-stable network browsing interface for client applications,
# for the version info x:y:z always will hold y=z
@ -626,10 +626,11 @@ AM_CONDITIONAL([HAVE_LIBSAMPLERATE], [test "x$HAVE_LIBSAMPLERATE" = x1])
HAVE_TDB=0
HAVE_GDBM=0
HAVE_SIMPLEDB=0
AC_ARG_WITH(
[database],
AS_HELP_STRING([--with-database=auto|tdb|gdbm],[Choose database backend.]),[],[with_database=auto])
AS_HELP_STRING([--with-database=auto|tdb|gdbm|simple],[Choose database backend.]),[],[with_database=auto])
if test "x${with_database}" = "xauto" -o "x${with_database}" = "xtdb" ; then
PKG_CHECK_MODULES(TDB, [ tdb ],
@ -659,7 +660,12 @@ if test "x${with_database}" = "xauto" -o "x${with_database}" = "xgdbm" ; then
fi
fi
if test "x${HAVE_TDB}" != x1 -a "x${HAVE_GDBM}" != x1; then
if test "x${with_database}" = "xauto" -o "x${with_database}" = "xsimple" ; then
HAVE_SIMPLEDB=1
with_database=simple
fi
if test "x${HAVE_TDB}" != x1 -a "x${HAVE_GDBM}" != x1 -a "x${HAVE_SIMPLEDB}" != x1; then
AC_MSG_ERROR([*** missing database backend])
fi
@ -671,6 +677,10 @@ if test "x${HAVE_GDBM}" = x1 ; then
AC_DEFINE([HAVE_GDBM], 1, [Have gdbm?])
fi
if test "x${HAVE_SIMPLEDB}" = x1 ; then
AC_DEFINE([HAVE_SIMPLEDB], 1, [Have simple?])
fi
AC_SUBST(TDB_CFLAGS)
AC_SUBST(TDB_LIBS)
AC_SUBST(HAVE_TDB)
@ -681,28 +691,47 @@ AC_SUBST(GDBM_LIBS)
AC_SUBST(HAVE_GDBM)
AM_CONDITIONAL([HAVE_GDBM], [test "x$HAVE_GDBM" = x1])
AC_SUBST(HAVE_SIMPLEDB)
AM_CONDITIONAL([HAVE_SIMPLEDB], [test "x$HAVE_SIMPLEDB" = x1])
#### OSS support (optional) ####
AC_ARG_ENABLE([oss],
AS_HELP_STRING([--disable-oss],[Disable optional OSS support]),
AC_ARG_ENABLE([oss-output],
AS_HELP_STRING([--disable-oss-output],[Disable optional OSS output support]),
[
case "${enableval}" in
yes) oss=yes ;;
no) oss=no ;;
*) AC_MSG_ERROR(bad value ${enableval} for --disable-oss) ;;
yes) oss_output=yes ;;
no) oss_output=no ;;
*) AC_MSG_ERROR(bad value ${enableval} for --disable-oss-output) ;;
esac
],
[oss=auto])
[oss_output=auto])
if test "x${oss}" != xno ; then
AC_ARG_ENABLE([oss-wrapper],
AS_HELP_STRING([--disable-oss-wrapper],[Disable optional OSS wrapper support]),
[
case "${enableval}" in
yes) oss_wrapper=yes ;;
no) oss_wrapper=no ;;
*) AC_MSG_ERROR(bad value ${enableval} for --disable-oss-wrapper) ;;
esac
],
[oss_wrapper=auto])
if test "x${oss_output}" != xno || test "x${oss_wrapper}" != "xno"; then
AC_CHECK_HEADERS([sys/soundcard.h],
[
if test "x${oss_output}" != "xno"; then
AC_DEFINE([HAVE_OSS_OUTPUT], 1, [Have OSS output?])
fi
if test "x${oss_wrapper}" != "xno"; then
AC_DEFINE([HAVE_OSS_WRAPPER], 1, [Have OSS wrapper (padsp)?])
fi
HAVE_OSS=1
AC_DEFINE([HAVE_OSS], 1, [Have OSS?])
],
[
HAVE_OSS=0
if test "x$oss" = xyes ; then
if test "x$oss_output" = xyes || test "x$oss_wrapper" = "xyes"; then
AC_MSG_ERROR([*** OSS support not found])
fi
])
@ -711,8 +740,8 @@ else
fi
AC_SUBST(HAVE_OSS)
AM_CONDITIONAL([HAVE_OSS], [test "x$HAVE_OSS" = x1])
AM_CONDITIONAL([HAVE_OSS_OUTPUT], [test "x$HAVE_OSS" = x1 && test "x${oss_output}" != "xno"])
AM_CONDITIONAL([HAVE_OSS_WRAPPER], [test "x$HAVE_OSS" = x1 && test "x${oss_wrapper}" != "xno"])
#### ALSA support (optional) ####
@ -912,12 +941,6 @@ AC_SUBST(AVAHI_LIBS)
AC_SUBST(HAVE_AVAHI)
AM_CONDITIONAL([HAVE_AVAHI], [test "x$HAVE_AVAHI" = x1])
### LIBOIL ####
PKG_CHECK_MODULES(LIBOIL, [ liboil-0.3 >= 0.3.0 ])
AC_SUBST(LIBOIL_CFLAGS)
AC_SUBST(LIBOIL_LIBS)
### JACK (optional) ####
AC_ARG_ENABLE([jack],
@ -1100,7 +1123,27 @@ AC_SUBST(UDEV_LIBS)
AC_SUBST(HAVE_UDEV)
AM_CONDITIONAL([HAVE_UDEV], [test "x$HAVE_UDEV" = x1])
AC_DEFINE([LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE], 1, [I know the API is subject to change.])
#### HAL compat support (optional) ####
AC_ARG_ENABLE([hal-compat],
AS_HELP_STRING([--disable-hal-compat],[Disable optional HAL->udev transition compatibility support]),
[
case "${enableval}" in
yes) halcompat=yes ;;
no) halcompat=no ;;
*) AC_MSG_ERROR(bad value ${enableval} for --disable-hal-compat) ;;
esac
],
[halcompat=auto])
if test "x${halcompat}" != xno -a "x$HAVE_HAL" = "x0" -a "x$HAVE_UDEV" = "x1" ; then
HAVE_HAL_COMPAT=1
AC_DEFINE([HAVE_HAL_COMPAT], 1, [Have HAL compatibility.])
else
HAVE_HAL_COMPAT=0
fi
AC_SUBST(HAVE_HAL_COMPAT)
AM_CONDITIONAL([HAVE_HAL_COMPAT], [test "x$HAVE_HAL_COMPAT" = x1])
#### BlueZ support (optional) ####
@ -1359,9 +1402,15 @@ if test "x$HAVE_X11" = "x1" ; then
ENABLE_X11=yes
fi
ENABLE_OSS=no
ENABLE_OSS_OUTPUT=no
ENABLE_OSS_WRAPPER=no
if test "x$HAVE_OSS" = "x1" ; then
ENABLE_OSS=yes
if test "x$enable_oss_output" != "xno"; then
ENABLE_OSS_OUTPUT=yes
fi
if test "x$enable_oss_wrapper" != "xno"; then
ENABLE_OSS_WRAPPER=yes
fi
fi
ENABLE_ALSA=no
@ -1419,6 +1468,11 @@ if test "x$HAVE_UDEV" = "x1" ; then
ENABLE_UDEV=yes
fi
ENABLE_HAL_COMPAT=no
if test "x$HAVE_HAL_COMPAT" = "x1" ; then
ENABLE_HAL_COMPAT=yes
fi
ENABLE_TCPWRAP=no
if test "x${LIBWRAP_LIBS}" != x ; then
ENABLE_TCPWRAP=yes
@ -1444,6 +1498,11 @@ if test "x${HAVE_TDB}" = "x1" ; then
ENABLE_TDB=yes
fi
ENABLE_SIMPLEDB=no
if test "x${HAVE_SIMPLEDB}" = "x1" ; then
ENABLE_SIMPLEDB=yes
fi
ENABLE_OPENSSL=no
if test "x${HAVE_OPENSSL}" = "x1" ; then
ENABLE_OPENSSL=yes
@ -1472,7 +1531,8 @@ echo "
CFLAGS: ${CFLAGS}
Have X11: ${ENABLE_X11}
Enable OSS: ${ENABLE_OSS}
Enable OSS Output: ${ENABLE_OSS_OUTPUT}
Enable OSS Wrapper: ${ENABLE_OSS_WRAPPER}
Enable Alsa: ${ENABLE_ALSA}
Enable Solaris: ${ENABLE_SOLARIS}
Enable GLib 2.0: ${ENABLE_GLIB20}
@ -1484,6 +1544,7 @@ echo "
Enable LIRC: ${ENABLE_LIRC}
Enable HAL: ${ENABLE_HAL}
Enable udev: ${ENABLE_UDEV}
Enable HAL->udev compat: ${ENABLE_HAL_COMPAT}
Enable BlueZ: ${ENABLE_BLUEZ}
Enable TCP Wrappers: ${ENABLE_TCPWRAP}
Enable libsamplerate: ${ENABLE_LIBSAMPLERATE}
@ -1491,6 +1552,7 @@ echo "
Enable OpenSSL (for Airtunes): ${ENABLE_OPENSSL}
Enable tdb: ${ENABLE_TDB}
Enable gdbm: ${ENABLE_GDBM}
Enable simple database: ${ENABLE_SIMPLEDB}
System User: ${PA_SYSTEM_USER}
System Group: ${PA_SYSTEM_GROUP}

View file

@ -417,7 +417,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = ../src/pulse/context.h ../src/pulse/stream.h ../src/pulse/pulseaudio.h ../src/pulse/sample.h ../src/pulse/def.h ../src/pulse/subscribe.h ../src/pulse/introspect.h ../src/pulse/scache.h ../src/pulse/mainloop-api.h ../src/pulse/glib-mainloop.h ../src/pulse/mainloop.h ../src/pulse/mainloop-signal.h ../src/pulse/error.h ../src/pulse/operation.h ../src/pulse/simple.h ../src/pulse/version.h ../src/pulse/volume.h ../src/pulse/channelmap.h ../src/pulse/thread-mainloop.h ../src/pulse/xmalloc.h ../src/pulse/utf8.h ../src/pulse/util.h ../src/pulse/timeval.h ../src/pulse/proplist.h ../src/pulse/gccmacro.h ../src/pulse/ext-stream-restore.h
INPUT = ../src/pulse/context.h ../src/pulse/stream.h ../src/pulse/pulseaudio.h ../src/pulse/sample.h ../src/pulse/def.h ../src/pulse/subscribe.h ../src/pulse/introspect.h ../src/pulse/scache.h ../src/pulse/mainloop-api.h ../src/pulse/glib-mainloop.h ../src/pulse/mainloop.h ../src/pulse/mainloop-signal.h ../src/pulse/error.h ../src/pulse/operation.h ../src/pulse/simple.h ../src/pulse/version.h ../src/pulse/volume.h ../src/pulse/channelmap.h ../src/pulse/thread-mainloop.h ../src/pulse/xmalloc.h ../src/pulse/utf8.h ../src/pulse/util.h ../src/pulse/timeval.h ../src/pulse/proplist.h ../src/pulse/gccmacro.h ../src/pulse/ext-stream-restore.h ../src/pulse/rtclock.h
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp

View file

@ -92,9 +92,9 @@ USA.
</option>
<option>
<p><opt>disable-shm=</opt> Disable data transfer via POSIX
<p><opt>enable-shm=</opt> Enable data transfer via POSIX
shared memory. Takes a boolean argument, defaults to
<opt>no</opt>.</p>
<opt>yes</opt>.</p>
</option>
<option>

View file

@ -65,20 +65,21 @@ USA.
</option>
<option>
<p><opt>disallow-module-loading=</opt> Disallow module loading
after startup. This is a security feature that makes sure that
no further modules may be loaded into the PulseAudio server
after startup completed. It is recommended to enable this when
<opt>system-instance</opt> is enabled. Please note that certain
features like automatic hot-plug support will not work if this
option is enabled. Takes a boolean argument, defaults to
<opt>no</opt>. The <opt>--disallow-module-loading</opt> command line
option takes precedence.</p>
<p><opt>allow-module-loading=</opt> Allow/disallow module
loading after startup. This is a security feature that if
dsabled makes sure that no further modules may be loaded into
the PulseAudio server after startup completed. It is recommended
to disable this when <opt>system-instance</opt> is
enabled. Please note that certain features like automatic
hot-plug support will not work if this option is enabled. Takes
a boolean argument, defaults to <opt>yes</opt>. The
<opt>--disallow-module-loading</opt> command line option takes
precedence.</p>
</option>
<option>
<p><opt>disallow-exit=</opt> Disallow exit on user
request. Defaults to <opt>no</opt>.</p>
<p><opt>allow-exit=</opt> Allow/disallow exit on user
request. Defaults to <opt>yes</opt>.</p>
</option>
<option>
@ -105,19 +106,19 @@ USA.
</option>
<option>
<p><opt>disable-remixing=</opt> Never upmix or downmix channels
to different channel maps. Instead, do a simple name-based
matching only.</p>
<p><opt>enable-remixing=</opt> If disabled never upmix or
downmix channels to different channel maps. Instead, do a simple
name-based matching only. Defaults to <opt>yes.</opt></p>
</option>
<option>
<p><opt>disable-lfe-remixing=</opt> When upmixing or downmixing
ignore LFE channels. When this option is on the output LFE
channel will only get a signal when an input LFE channel is
available as well. If no input LFE channel is available the
output LFE channel will always be 0. If no output LFE channel is
available the signal on the input LFE channel will be
ignored. Defaults to "on".</p>
<p><opt>enable-lfe-remixing=</opt> if disabeld when upmixing or
downmixing ignore LFE channels. When this option is dsabled the
output LFE channel will only get a signal when an input LFE
channel is available as well. If no input LFE channel is
available the output LFE channel will always be 0. If no output
LFE channel is available the signal on the input LFE channel
will be ignored. Defaults to <opt>no</opt>.</p>
</option>
<option>
@ -132,12 +133,12 @@ USA.
</option>
<option>
<p><opt>no-cpu-limit=</opt> Do not install the CPU load limiter,
even on platforms where it is supported. This option is useful
when debugging/profiling PulseAudio to disable disturbing
SIGXCPU signals. Takes a boolean argument, defaults to <opt>no</opt>. The
<opt>--no-cpu-limit</opt> command line argument takes
precedence.</p>
<p><opt>cpu-limit=</opt> If disabled do not install the CPU load
limiter, even on platforms where it is supported. This option is
useful when debugging/profiling PulseAudio to disable disturbing
SIGXCPU signals. Takes a boolean argument, defaults to
<opt>no</opt>. The <opt>--no-cpu-limit</opt> command line
argument takes precedence.</p>
</option>
<option>
@ -148,9 +149,9 @@ USA.
</option>
<option>
<p><opt>disable-shm=</opt> Disable data transfer via POSIX
<p><opt>enable-shm=</opt> Enable data transfer via POSIX
shared memory. Takes a boolean argument, defaults to
<opt>no</opt>. The <opt>--disable-shm</opt> command line
<opt>yes</opt>. The <opt>--disable-shm</opt> command line
argument takes precedence.</p>
</option>
@ -204,7 +205,7 @@ USA.
real-time. The controlling thread is left a normally scheduled
thread. Thus enabling the high-priority option is orthogonal.
See <manref section="1" name="pulseaudio"/> for more
information. Takes a boolean argument, defaults to "no". The
information. Takes a boolean argument, defaults to "yes". The
<opt>--realtime</opt> command line option takes precedence.</p>
</option>
@ -230,7 +231,7 @@ USA.
<option>
<p><opt>exit-idle-time=</opt> Terminate the daemon after the
last client quit and this time in seconds passed. Use a negative value to
disable this feature. Defaults to -1. The
disable this feature. Defaults to 20. The
<opt>--exit-idle-time</opt> command line option takes
precedence.</p>
</option>

View file

@ -2,8 +2,8 @@ as
bn_IN
ca
cs
de_CH
de
de_CH
el
es
fi
@ -11,16 +11,18 @@ fr
gu
hi
it
ja
kn
ml
mr
nl
or
pa
pl
pt_BR
pt
sr@latin
pt_BR
sr
sr@latin
sv
ta
te

View file

@ -55,7 +55,6 @@ src/pulsecore/queue.c
src/pulsecore/core.c
#src/pulsecore/shmasyncq.c
src/pulsecore/x11wrap.c
src/pulsecore/rtclock.c
src/pulsecore/ioline.c
src/pulsecore/asyncq.c
src/pulsecore/mutex-posix.c
@ -99,7 +98,6 @@ src/pulsecore/strlist.c
src/pulsecore/msgobject.c
src/pulsecore/mutex-win32.c
src/pulsecore/dynarray.c
src/pulsecore/rtsig.c
src/pulsecore/once.c
src/pulsecore/source.c
src/pulsecore/memchunk.c
@ -149,14 +147,12 @@ src/pulsecore/protocol-http.c
src/pulsecore/semaphore-win32.c
src/daemon/cpulimit.c
src/daemon/ltdl-bind-now.c
src/daemon/polkit.c
src/daemon/main.c
src/daemon/cmdline.c
src/daemon/dumpmodules.c
src/daemon/daemon-conf.c
src/daemon/caps.c
src/daemon/pulseaudio.desktop.in
src/daemon/org.pulseaudio.policy.in
src/pulse/channelmap.c
src/pulse/error.c
src/pulse/proplist.c

1321
po/as.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1414
po/ca.po

File diff suppressed because it is too large Load diff

1357
po/cs.po

File diff suppressed because it is too large Load diff

1240
po/de.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1004
po/el.po

File diff suppressed because it is too large Load diff

1378
po/es.po

File diff suppressed because it is too large Load diff

1396
po/fi.po

File diff suppressed because it is too large Load diff

1235
po/fr.po

File diff suppressed because it is too large Load diff

1393
po/gu.po

File diff suppressed because it is too large Load diff

1309
po/hi.po

File diff suppressed because it is too large Load diff

1428
po/it.po

File diff suppressed because it is too large Load diff

2033
po/ja.po Normal file

File diff suppressed because it is too large Load diff

1325
po/kn.po

File diff suppressed because it is too large Load diff

2350
po/ml.po Normal file

File diff suppressed because it is too large Load diff

1330
po/mr.po

File diff suppressed because it is too large Load diff

1617
po/nl.po

File diff suppressed because it is too large Load diff

1378
po/or.po

File diff suppressed because it is too large Load diff

1309
po/pa.po

File diff suppressed because it is too large Load diff

1213
po/pl.po

File diff suppressed because it is too large Load diff

1292
po/pt.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1432
po/sr.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1113
po/sv.po

File diff suppressed because it is too large Load diff

1338
po/ta.po

File diff suppressed because it is too large Load diff

1306
po/te.po

File diff suppressed because it is too large Load diff

1370
po/uk.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1463
pulseaudio.vapi Normal file

File diff suppressed because it is too large Load diff

1
src/.gitignore vendored
View file

@ -1,3 +1,4 @@
usergroup-test
sigbus-test
TAGS
alsa-time-test

View file

@ -32,6 +32,7 @@ xdgautostartdir=$(sysconfdir)/xdg/autostart
alsaprofilesetsdir=$(datadir)/pulseaudio/alsa-mixer/profile-sets
alsapathsdir=$(datadir)/pulseaudio/alsa-mixer/paths
udevrulesdir=/lib/udev/rules.d
dbuspolicydir=$(sysconfdir)/dbus-1/system.d
###################################
# Defines #
@ -73,6 +74,7 @@ AM_CFLAGS = \
$(LIBSAMPLERATE_CFLAGS) \
$(LIBSNDFILE_CFLAGS) \
$(LIBSPEEX_CFLAGS) \
-DPA_BUILDDIR=\"$(abs_builddir)\" \
-DPA_DLSEARCHPATH=\"$(modlibexecdir)\" \
-DPA_DEFAULT_CONFIG_DIR=\"$(PA_DEFAULT_CONFIG_DIR)\" \
-DPA_BINARY=\"$(PA_BINARY)\" \
@ -119,6 +121,7 @@ EXTRA_DIST = \
modules/module-defs.h.m4 \
daemon/pulseaudio.desktop.in \
map-file \
daemon/pulseaudio-system.conf \
modules/alsa/mixer/profile-sets/default.conf \
modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \
modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf \
@ -145,6 +148,9 @@ pulseconf_DATA = \
daemon.conf \
client.conf
dbuspolicy_DATA = \
daemon/pulseaudio-system.conf
if HAVE_X11
xdgautostart_in_files = \
daemon/pulseaudio.desktop.in
@ -171,8 +177,8 @@ pulseaudio_SOURCES = \
daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \
daemon/main.c
pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(LIBOIL_CFLAGS) $(DBUS_CFLAGS)
pulseaudio_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(LIBOIL_LIBS) $(DBUS_LIBS)
pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(DBUS_CFLAGS)
pulseaudio_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(DBUS_LIBS)
# This is needed because automake doesn't properly expand the foreach below
pulseaudio_DEPENDENCIES = libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(PREOPEN_LIBS)
@ -274,7 +280,8 @@ TESTS = \
proplist-test \
lock-autospawn-test \
prioq-test \
sigbus-test
sigbus-test \
usergroup-test
TESTS_BINARIES = \
mainloop-test \
@ -312,7 +319,8 @@ TESTS_BINARIES = \
stripnul \
lock-autospawn-test \
prioq-test \
sigbus-test
sigbus-test \
usergroup-test
if HAVE_SIGXCPU
#TESTS += \
@ -488,18 +496,18 @@ sig2str_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
resampler_test_SOURCES = tests/resampler-test.c
resampler_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
resampler_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
resampler_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
resampler_test_CFLAGS = $(AM_CFLAGS)
resampler_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
mix_test_SOURCES = tests/mix-test.c
mix_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
mix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
mix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
mix_test_CFLAGS = $(AM_CFLAGS)
mix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
remix_test_SOURCES = tests/remix-test.c
remix_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
remix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
remix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
remix_test_CFLAGS = $(AM_CFLAGS)
remix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
smoother_test_SOURCES = tests/smoother-test.c
smoother_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
@ -508,38 +516,38 @@ smoother_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
envelope_test_SOURCES = tests/envelope-test.c
envelope_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
envelope_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
envelope_test_CFLAGS = $(AM_CFLAGS)
envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
proplist_test_SOURCES = tests/proplist-test.c
proplist_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
proplist_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
proplist_test_CFLAGS = $(AM_CFLAGS)
proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
rtstutter_SOURCES = tests/rtstutter.c
rtstutter_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
rtstutter_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
rtstutter_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
rtstutter_CFLAGS = $(AM_CFLAGS)
rtstutter_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
stripnul_SOURCES = tests/stripnul.c
stripnul_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
stripnul_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
stripnul_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
stripnul_CFLAGS = $(AM_CFLAGS)
stripnul_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
lock_autospawn_test_SOURCES = tests/lock-autospawn-test.c
lock_autospawn_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
lock_autospawn_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
lock_autospawn_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
lock_autospawn_test_CFLAGS = $(AM_CFLAGS)
lock_autospawn_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
prioq_test_SOURCES = tests/prioq-test.c
prioq_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
prioq_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
prioq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
prioq_test_CFLAGS = $(AM_CFLAGS)
prioq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
sigbus_test_SOURCES = tests/sigbus-test.c
sigbus_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la
sigbus_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
sigbus_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
sigbus_test_CFLAGS = $(AM_CFLAGS)
sigbus_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
gtk_test_SOURCES = tests/gtk-test.c
gtk_test_LDADD = $(AM_LDADD) libpulse.la libpulse-mainloop-glib.la
@ -551,6 +559,11 @@ alsa_time_test_LDADD = $(AM_LDADD)
alsa_time_test_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
alsa_time_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(ASOUNDLIB_LIBS)
usergroup_test_SOURCES = tests/usergroup-test.c
usergroup_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la
usergroup_test_CFLAGS = $(AM_CFLAGS)
usergroup_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
###################################
# Common library #
###################################
@ -615,6 +628,7 @@ 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/usergroup.c pulsecore/usergroup.h \
pulsecore/sndfile-util.c pulsecore/sndfile-util.h \
pulsecore/winsock.h
@ -776,7 +790,7 @@ libpulse_mainloop_glib_la_LDFLAGS = $(AM_LDFLAGS) $(VERSIONING_LDFLAGS) -version
# OSS emulation #
###################################
if HAVE_OSS
if HAVE_OSS_WRAPPER
lib_LTLIBRARIES += libpulsedsp.la
bin_SCRIPTS += utils/padsp
endif
@ -817,11 +831,18 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \
pulsecore/object.c pulsecore/object.h \
pulsecore/play-memblockq.c pulsecore/play-memblockq.h \
pulsecore/play-memchunk.c pulsecore/play-memchunk.h \
pulsecore/remap.c pulsecore/remap.h \
pulsecore/remap_mmx.c pulsecore/remap_sse.c \
pulsecore/resampler.c pulsecore/resampler.h \
pulsecore/rtpoll.c pulsecore/rtpoll.h \
pulsecore/sample-util.c pulsecore/sample-util.h \
pulsecore/cpu-arm.c pulsecore/cpu-arm.h \
pulsecore/cpu-x86.c pulsecore/cpu-x86.h \
pulsecore/svolume_c.c pulsecore/svolume_arm.c \
pulsecore/svolume_mmx.c pulsecore/svolume_sse.c \
pulsecore/sconv-s16be.c pulsecore/sconv-s16be.h \
pulsecore/sconv-s16le.c pulsecore/sconv-s16le.h \
pulsecore/sconv_sse.c \
pulsecore/sconv.c pulsecore/sconv.h \
pulsecore/shared.c pulsecore/shared.h \
pulsecore/shm.c pulsecore/shm.h \
@ -837,9 +858,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \
pulsecore/time-smoother.c pulsecore/time-smoother.h \
pulsecore/database.h
libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(WINSOCK_CFLAGS) $(LIBOIL_CFLAGS)
libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(WINSOCK_CFLAGS)
libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS = -avoid-version
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
libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(WINSOCK_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
@ -865,6 +886,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(TDB_CFLAGS)
libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(TDB_LIBS)
endif
if HAVE_SIMPLEDB
libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/database-simple.c
endif
# We split the foreign code off to not be annoyed by warnings we don't care about
noinst_LTLIBRARIES = libpulsecore-foreign.la
@ -987,8 +1011,8 @@ modlibexec_LTLIBRARIES += \
module-tunnel-source.la \
module-position-event-sounds.la \
module-augment-properties.la \
module-cork-music-on-phone.la
module-cork-music-on-phone.la \
module-loopback.la
# See comment at librtp.la above
if !OS_IS_WIN32
@ -1031,7 +1055,7 @@ modlibexec_LTLIBRARIES += \
module-x11-cork-request.la
endif
if HAVE_OSS
if HAVE_OSS_OUTPUT
modlibexec_LTLIBRARIES += \
liboss-util.la \
module-oss.la
@ -1120,6 +1144,11 @@ modlibexec_LTLIBRARIES += \
module-hal-detect.la
endif
if HAVE_HAL_COMPAT
modlibexec_LTLIBRARIES += \
module-hal-detect.la
endif
if HAVE_UDEV
modlibexec_LTLIBRARIES += \
module-udev-detect.la
@ -1221,7 +1250,8 @@ SYMDEF_FILES = \
modules/module-position-event-sounds-symdef.h \
modules/module-augment-properties-symdef.h \
modules/module-cork-music-on-phone-symdef.h \
modules/module-console-kit-symdef.h
modules/module-console-kit-symdef.h \
modules/module-loopback-symdef.h
EXTRA_DIST += $(SYMDEF_FILES)
BUILT_SOURCES += $(SYMDEF_FILES)
@ -1363,6 +1393,10 @@ module_tunnel_source_la_SOURCES = modules/module-tunnel.c
module_tunnel_source_la_LDFLAGS = $(MODULE_LDFLAGS)
module_tunnel_source_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
module_loopback_la_SOURCES = modules/module-loopback.c
module_loopback_la_LDFLAGS = $(MODULE_LDFLAGS)
module_loopback_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
# X11
module_x11_bell_la_SOURCES = modules/x11/module-x11-bell.c
@ -1576,10 +1610,16 @@ 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)
if HAVE_HAL_COMPAT
module_hal_detect_la_SOURCES = modules/module-hal-detect-compat.c
module_hal_detect_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
module_hal_detect_la_CFLAGS = $(AM_CFLAGS)
else
module_hal_detect_la_SOURCES = modules/module-hal-detect.c
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)
endif
module_hal_detect_la_LDFLAGS = $(MODULE_LDFLAGS)
module_udev_detect_la_SOURCES = modules/module-udev-detect.c
module_udev_detect_la_LDFLAGS = $(MODULE_LDFLAGS)
@ -1698,7 +1738,7 @@ daemon.conf: daemon/daemon.conf.in Makefile
-e 's,@PA_DEFAULT_CONFIG_FILE\@,$(DEFAULT_CONFIG_DIR),g' < $< > $@
install-exec-hook:
chown root $(DESTDIR)$(bindir)/pulseaudio ; true
-chown root $(DESTDIR)$(pulselibexecdir)/proximity-helper
-chmod u+s $(DESTDIR)$(pulselibexecdir)/proximity-helper
ln -sf pacat $(DESTDIR)$(bindir)/parec
ln -sf pacat $(DESTDIR)$(bindir)/pamon

View file

@ -57,24 +57,29 @@ void pa_drop_root(void) {
#ifdef HAVE_GETUID
uid_t uid;
gid_t gid;
pa_log_debug(_("Cleaning up privileges."));
uid = getuid();
if (uid == 0 || geteuid() != 0)
return;
pa_log_info(_("Dropping root privileges."));
gid = getgid();
#if defined(HAVE_SETRESUID)
pa_assert_se(setresuid(uid, uid, uid) >= 0);
pa_assert_se(setresgid(gid, gid, gid) >= 0);
#elif defined(HAVE_SETREUID)
pa_assert_se(setreuid(uid, uid) >= 0);
pa_assert_se(setregid(gid, gid) >= 0);
#else
pa_assert_se(setuid(uid) >= 0);
pa_assert_se(seteuid(uid) >= 0);
pa_assert_se(setgid(gid) >= 0);
pa_assert_se(setegid(gid) >= 0);
#endif
pa_assert_se(getuid() == uid);
pa_assert_se(geteuid() == uid);
pa_assert_se(getgid() == gid);
pa_assert_se(getegid() == gid);
#endif
#ifdef HAVE_SYS_PRCTL_H
@ -82,7 +87,7 @@ void pa_drop_root(void) {
#endif
#ifdef HAVE_SYS_CAPABILITY_H
{
if (uid != 0) {
cap_t caps;
pa_assert_se(caps = cap_init());
pa_assert_se(cap_clear(caps) == 0);

View file

@ -385,11 +385,6 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d
pa_xfree(conf->script_commands);
conf->script_commands = pa_strbuf_tostring_free(buf);
if (!conf->script_commands) {
pa_xfree(conf->script_commands);
conf->script_commands = NULL;
}
*d = optind;
return 0;

View file

@ -83,7 +83,7 @@ static const pa_daemon_conf default_conf = {
.config_file = NULL,
.use_pid_file = TRUE,
.system_instance = FALSE,
.no_cpu_limit = FALSE,
.no_cpu_limit = TRUE,
.disable_shm = FALSE,
.lock_memory = FALSE,
.default_n_fragments = 4,
@ -133,9 +133,25 @@ static const pa_daemon_conf default_conf = {
};
pa_daemon_conf* pa_daemon_conf_new(void) {
pa_daemon_conf *c = pa_xnewdup(pa_daemon_conf, &default_conf, 1);
pa_daemon_conf *c;
c = pa_xnewdup(pa_daemon_conf, &default_conf, 1);
#if defined(__linux__) && !defined(__OPTIMIZE__)
/* We abuse __OPTIMIZE__ as a check whether we are a debug build
* or not. If we are and are run from the build tree then we
* override the search path to point to our build tree */
if (pa_run_from_build_tree()) {
pa_log_notice("Detected that we are run from the build tree, fixing search path.");
c->dl_search_path = pa_xstrdup(PA_BUILDDIR "/.libs/");
} else
#endif
c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH);
return c;
}
@ -441,11 +457,15 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
{ "high-priority", pa_config_parse_bool, &c->high_priority, NULL },
{ "realtime-scheduling", pa_config_parse_bool, &c->realtime_scheduling, NULL },
{ "disallow-module-loading", pa_config_parse_bool, &c->disallow_module_loading, NULL },
{ "allow-module-loading", pa_config_parse_not_bool, &c->disallow_module_loading, NULL },
{ "disallow-exit", pa_config_parse_bool, &c->disallow_exit, NULL },
{ "allow-exit", pa_config_parse_not_bool, &c->disallow_exit, NULL },
{ "use-pid-file", pa_config_parse_bool, &c->use_pid_file, NULL },
{ "system-instance", pa_config_parse_bool, &c->system_instance, NULL },
{ "no-cpu-limit", pa_config_parse_bool, &c->no_cpu_limit, NULL },
{ "cpu-limit", pa_config_parse_not_bool, &c->no_cpu_limit, NULL },
{ "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL },
{ "enable-shm", pa_config_parse_not_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 },
@ -465,7 +485,9 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
{ "default-fragment-size-msec", parse_fragment_size_msec, c, NULL },
{ "nice-level", parse_nice_level, c, NULL },
{ "disable-remixing", pa_config_parse_bool, &c->disable_remixing, NULL },
{ "enable-remixing", pa_config_parse_not_bool, &c->disable_remixing, NULL },
{ "disable-lfe-remixing", pa_config_parse_bool, &c->disable_lfe_remixing, NULL },
{ "enable-lfe-remixing", pa_config_parse_not_bool, &c->disable_lfe_remixing, NULL },
{ "load-default-script-file", pa_config_parse_bool, &c->load_default_script_file, NULL },
{ "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL },
{ "log-meta", pa_config_parse_bool, &c->log_meta, NULL },
@ -623,12 +645,12 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
pa_strbuf_printf(s, "nice-level = %i\n", c->nice_level);
pa_strbuf_printf(s, "realtime-scheduling = %s\n", pa_yes_no(c->realtime_scheduling));
pa_strbuf_printf(s, "realtime-priority = %i\n", c->realtime_priority);
pa_strbuf_printf(s, "disallow-module-loading = %s\n", pa_yes_no(c->disallow_module_loading));
pa_strbuf_printf(s, "disallow-exit = %s\n", pa_yes_no(c->disallow_exit));
pa_strbuf_printf(s, "allow-module-loading = %s\n", pa_yes_no(!c->disallow_module_loading));
pa_strbuf_printf(s, "allow-exit = %s\n", pa_yes_no(!c->disallow_exit));
pa_strbuf_printf(s, "use-pid-file = %s\n", pa_yes_no(c->use_pid_file));
pa_strbuf_printf(s, "system-instance = %s\n", pa_yes_no(c->system_instance));
pa_strbuf_printf(s, "no-cpu-limit = %s\n", pa_yes_no(c->no_cpu_limit));
pa_strbuf_printf(s, "disable-shm = %s\n", pa_yes_no(c->disable_shm));
pa_strbuf_printf(s, "cpu-limit = %s\n", pa_yes_no(!c->no_cpu_limit));
pa_strbuf_printf(s, "enable-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);
@ -639,8 +661,8 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
pa_strbuf_printf(s, "log-target = %s\n", c->auto_log_target ? "auto" : (c->log_target == PA_LOG_SYSLOG ? "syslog" : "stderr"));
pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]);
pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method));
pa_strbuf_printf(s, "disable-remixing = %s\n", pa_yes_no(c->disable_remixing));
pa_strbuf_printf(s, "disable-lfe-remixing = %s\n", pa_yes_no(c->disable_lfe_remixing));
pa_strbuf_printf(s, "enable-remixing = %s\n", pa_yes_no(!c->disable_remixing));
pa_strbuf_printf(s, "enable-lfe-remixing = %s\n", pa_yes_no(!c->disable_lfe_remixing));
pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format));
pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate);
pa_strbuf_printf(s, "default-sample-channels = %u\n", c->default_sample_spec.channels);

View file

@ -21,14 +21,14 @@
; daemonize = no
; fail = yes
; disallow-module-loading = no
; disallow-exit = no
; allow-module-loading = yes
; allow-exit = yes
; use-pid-file = yes
; system-instance = no
; disable-shm = no
; enable-shm = yes
; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB
; lock-memory = no
; no-cpu-limit = no
; cpu-limit = no
; high-priority = yes
; nice-level = -11
@ -51,8 +51,8 @@
; log-backtrace = 0
; resample-method = speex-float-3
; disable-remixing = no
; disable-lfe-remixing = yes
; enable-remixing = yes
; enable-lfe-remixing = no
; flat-volumes = yes

View file

@ -39,8 +39,6 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <liboil/liboil.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
@ -95,6 +93,8 @@
#ifdef HAVE_DBUS
#include <pulsecore/dbus-shared.h>
#endif
#include <pulsecore/cpu-arm.h>
#include <pulsecore/cpu-x86.h>
#include "cmdline.h"
#include "cpulimit.h"
@ -109,7 +109,7 @@ int allow_severity = LOG_INFO;
int deny_severity = LOG_WARNING;
#endif
#ifdef HAVE_OSS
#ifdef HAVE_OSS_WRAPPER
/* padsp looks for this symbol in the running process and disables
* itself if it finds it and it is set to 7 (which is actually a bit
* mask). For details see padsp. */
@ -259,8 +259,13 @@ static int change_user(void) {
pa_set_env("HOME", PA_SYSTEM_RUNTIME_PATH);
/* Relevant for pa_runtime_path() */
if (!getenv("PULSE_RUNTIME_PATH"))
pa_set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH);
if (!getenv("PULSE_CONFIG_PATH"))
pa_set_env("PULSE_CONFIG_PATH", PA_SYSTEM_CONFIG_PATH);
if (!getenv("PULSE_STATE_PATH"))
pa_set_env("PULSE_STATE_PATH", PA_SYSTEM_STATE_PATH);
pa_log_info(_("Successfully dropped root privileges."));
@ -401,6 +406,36 @@ int main(int argc, char *argv[]) {
pa_log_set_level(PA_LOG_NOTICE);
pa_log_set_flags(PA_LOG_COLORS|PA_LOG_PRINT_FILE|PA_LOG_PRINT_LEVEL, PA_LOG_RESET);
#if defined(__linux__) && defined(__OPTIMIZE__)
/*
Disable lazy relocations to make usage of external libraries
more deterministic for our RT threads. We abuse __OPTIMIZE__ as
a check whether we are a debug build or not. This all is
admittedly a bit snake-oilish.
*/
if (!getenv("LD_BIND_NOW")) {
char *rp;
/* We have to execute ourselves, because the libc caches the
* value of $LD_BIND_NOW on initialization. */
pa_set_env("LD_BIND_NOW", "1");
if ((rp = pa_readlink("/proc/self/exe"))) {
if (pa_streq(rp, PA_BINARY))
pa_assert_se(execv(rp, argv) == 0);
else
pa_log_warn("/proc/self/exe does not point to " PA_BINARY ", cannot self execute. Are you playing games?");
pa_xfree(rp);
} else
pa_log_warn("Couldn't read /proc/self/exe, cannot self execute. Running in a chroot()?");
}
#endif
if ((e = getenv("PULSE_PASSED_FD"))) {
passed_fd = atoi(e);
@ -411,10 +446,13 @@ int main(int argc, char *argv[]) {
/* We might be autospawned, in which case have no idea in which
* context we have been started. Let's cleanup our execution
* context as good as possible */
pa_reset_personality();
pa_drop_root();
pa_close_all(passed_fd, -1);
pa_reset_sigs(-1);
pa_unblock_sigs(-1);
pa_reset_priority();
setlocale(LC_ALL, "");
pa_init_i18n();
@ -668,7 +706,7 @@ int main(int argc, char *argv[]) {
#endif
}
pa_set_env("PULSE_INTERNAL", "1");
pa_set_env_and_record("PULSE_INTERNAL", "1");
pa_assert_se(chdir("/") == 0);
umask(0022);
@ -683,7 +721,7 @@ int main(int argc, char *argv[]) {
if (change_user() < 0)
goto finish;
pa_set_env("PULSE_SYSTEM", conf->system_instance ? "1" : "0");
pa_set_env_and_record("PULSE_SYSTEM", conf->system_instance ? "1" : "0");
pa_log_info(_("This is PulseAudio %s"), PACKAGE_VERSION);
pa_log_debug(_("Compilation host: %s"), CANONICAL_HOST);
@ -741,6 +779,8 @@ int main(int argc, char *argv[]) {
pa_log_info(_("Using state directory %s."), s);
pa_xfree(s);
pa_log_info(_("Using modules directory %s."), conf->dl_search_path);
pa_log_info(_("Running in system mode: %s"), pa_yes_no(pa_in_system_mode()));
if (pa_in_system_mode())
@ -788,6 +828,11 @@ int main(int argc, char *argv[]) {
pa_memtrap_install();
if (!getenv("PULSE_NO_SIMD")) {
pa_cpu_init_x86();
pa_cpu_init_arm();
}
pa_assert_se(mainloop = pa_mainloop_new());
if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm, conf->shm_size))) {
@ -827,8 +872,6 @@ int main(int argc, char *argv[]) {
win32_timer = pa_mainloop_get_api(mainloop)->rtclock_time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&win32_tv), message_cb, NULL);
#endif
oil_init();
if (!conf->no_cpu_limit)
pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0);
@ -927,6 +970,9 @@ finish:
if (valid_pid_file)
pa_pid_file_remove();
/* This has no real purpose except making things valgrind-clean */
pa_unset_env_recorded();
#ifdef OS_IS_WIN32
WSACleanup();
#endif

View file

@ -1,172 +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 <unistd.h>
#include <stdlib.h>
#include <inttypes.h>
#include <dbus/dbus.h>
#include <polkit-dbus/polkit-dbus.h>
#include <pulse/i18n.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include "polkit.h"
int pa_polkit_check(const char *action_id) {
int ret = -1;
DBusError dbus_error;
DBusConnection *bus = NULL;
PolKitCaller *caller = NULL;
PolKitAction *action = NULL;
PolKitContext *context = NULL;
PolKitError *polkit_error = NULL;
PolKitSession *session = NULL;
PolKitResult polkit_result;
dbus_error_init(&dbus_error);
if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error))) {
pa_log_error(_("Cannot connect to system bus: %s"), dbus_error.message);
goto finish;
}
/* There seems to be a bug in some versions of D-Bus that causes
* dbus_shutdown() to call exit() when a connection without this
* flag disabled was created during runtime.*/
dbus_connection_set_exit_on_disconnect(bus, FALSE);
if (!(caller = polkit_caller_new_from_pid(bus, getpid(), &dbus_error))) {
pa_log_error(_("Cannot get caller from PID: %s"), dbus_error.message);
goto finish;
}
/* This function is called when PulseAudio is called SUID root. We
* want to authenticate the real user that called us and not the
* effective user we gained through being SUID root. Hence we
* overwrite the UID caller data here explicitly, just for
* paranoia. In fact PolicyKit should fill in the UID here anyway
* -- an not the EUID or any other user id. */
if (!(polkit_caller_set_uid(caller, getuid()))) {
pa_log_error(_("Cannot set UID on caller object."));
goto finish;
}
if (!(polkit_caller_get_ck_session(caller, &session))) {
pa_log_error(_("Failed to get CK session."));
goto finish;
}
/* We need to overwrite the UID in both the caller and the session
* object */
if (!(polkit_session_set_uid(session, getuid()))) {
pa_log_error(_("Cannot set UID on session object."));
goto finish;
}
if (!(action = polkit_action_new())) {
pa_log_error(_("Cannot allocate PolKitAction."));
goto finish;
}
if (!polkit_action_set_action_id(action, action_id)) {
pa_log_error(_("Cannot set action_id"));
goto finish;
}
if (!(context = polkit_context_new())) {
pa_log_error(_("Cannot allocate PolKitContext."));
goto finish;
}
if (!polkit_context_init(context, &polkit_error)) {
pa_log_error(_("Cannot initialize PolKitContext: %s"), polkit_error_get_error_message(polkit_error));
goto finish;
}
for (;;) {
polkit_result = polkit_context_is_caller_authorized(context, action, caller, TRUE, &polkit_error);
if (polkit_error_is_set(polkit_error)) {
pa_log_error(_("Could not determine whether caller is authorized: %s"), polkit_error_get_error_message(polkit_error));
goto finish;
}
if (polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH ||
polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_SESSION ||
polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_ALWAYS ||
polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT ||
polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH ||
polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_SESSION ||
polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_ALWAYS ||
polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT
) {
if (polkit_auth_obtain(action_id, 0, getpid(), &dbus_error)) {
polkit_result = POLKIT_RESULT_YES;
break;
}
if (dbus_error_is_set(&dbus_error)) {
pa_log_error(_("Cannot obtain auth: %s"), dbus_error.message);
goto finish;
}
}
break;
}
if (polkit_result != POLKIT_RESULT_YES && polkit_result != POLKIT_RESULT_NO)
pa_log_warn(_("PolicyKit responded with '%s'"), polkit_result_to_string_representation(polkit_result));
ret = polkit_result == POLKIT_RESULT_YES;
finish:
if (caller)
polkit_caller_unref(caller);
if (action)
polkit_action_unref(action);
if (context)
polkit_context_unref(context);
if (bus)
dbus_connection_unref(bus);
dbus_error_free(&dbus_error);
if (polkit_error)
polkit_error_free(polkit_error);
return ret;
}

View file

@ -0,0 +1,37 @@
<?xml version="1.0"?><!--*-nxml-*-->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!--
This file is part of PulseAudio.
PulseAudio is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
-->
<busconfig>
<!-- System-wide PulseAudio runs as 'pulse' user. This fragment is
not necessary for user PulseAudio instances. -->
<policy user="pulse">
<allow own="org.pulseaudio.Server"/>
<!-- Allow pulseaudio to talk to HAL for device detection -->
<allow send_destination="org.freedesktop.Hal" send_interface="org.freedesktop.Hal.Manager"/>
<allow send_destination="org.freedesktop.Hal" send_interface="org.freedesktop.Hal.Device"/>
</policy>
</busconfig>

View file

@ -123,13 +123,18 @@ pa_cvolume_avg_mask;
pa_cvolume_channels_equal_to;
pa_cvolume_compatible;
pa_cvolume_compatible_with_channel_map;
pa_cvolume_dec;
pa_cvolume_equal;
pa_cvolume_get_balance;
pa_cvolume_get_fade;
pa_cvolume_get_position;
pa_cvolume_inc;
pa_cvolume_init;
pa_cvolume_max;
pa_cvolume_max_mask;
pa_cvolume_merge;
pa_cvolume_min;
pa_cvolume_min_mask;
pa_cvolume_remap;
pa_cvolume_scale;
pa_cvolume_scale_mask;
@ -219,6 +224,8 @@ pa_simple_get_latency;
pa_simple_new;
pa_simple_read;
pa_simple_write;
pa_stream_begin_write;
pa_stream_cancel_write;
pa_stream_connect_playback;
pa_stream_connect_record;
pa_stream_connect_upload;

View file

@ -929,7 +929,7 @@ static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) {
int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
pa_alsa_element *e;
int r;
int r = 0;
pa_assert(m);
pa_assert(p);
@ -940,7 +940,6 @@ int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
PA_LLIST_FOREACH(e, p->elements) {
switch (e->switch_use) {
case PA_ALSA_SWITCH_MUTE:
case PA_ALSA_SWITCH_OFF:
r = element_set_switch(e, m, FALSE);
break;
@ -949,6 +948,7 @@ int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
r = element_set_switch(e, m, TRUE);
break;
case PA_ALSA_SWITCH_MUTE:
case PA_ALSA_SWITCH_IGNORE:
case PA_ALSA_SWITCH_SELECT:
r = 0;
@ -960,7 +960,6 @@ int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
switch (e->volume_use) {
case PA_ALSA_VOLUME_OFF:
case PA_ALSA_VOLUME_MERGE:
r = element_mute_volume(e, m);
break;
@ -968,6 +967,7 @@ int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
r = element_zero_volume(e, m);
break;
case PA_ALSA_VOLUME_MERGE:
case PA_ALSA_VOLUME_IGNORE:
r = 0;
break;
@ -1849,7 +1849,12 @@ pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction)
items[1].data = &p->description;
items[2].data = &p->name;
fn = pa_maybe_prefix_path(fname, PA_ALSA_PATHS_DIR);
fn = pa_maybe_prefix_path(fname,
#if defined(__linux__) && !defined(__OPTIMIZE__)
pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/paths/" :
#endif
PA_ALSA_PATHS_DIR);
r = pa_config_parse(fn, NULL, items, p);
pa_xfree(fn);
@ -2838,9 +2843,9 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
if (bonus) {
if (pa_channel_map_equal(&m->channel_map, bonus))
m->priority += 5000;
m->priority += 50;
else if (m->channel_map.channels == bonus->channels)
m->priority += 4000;
m->priority += 30;
}
return 0;
@ -2884,7 +2889,7 @@ static void profile_set_add_auto_pair(
else
name = pa_sprintf_malloc("input:%s", n->name);
if ((p = pa_hashmap_get(ps->profiles, name))) {
if (pa_hashmap_get(ps->profiles, name)) {
pa_xfree(name);
return;
}
@ -3110,7 +3115,12 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel
if (!fname)
fname = "default.conf";
fn = pa_maybe_prefix_path(fname, PA_ALSA_PROFILE_SETS_DIR);
fn = pa_maybe_prefix_path(fname,
#if defined(__linux__) && !defined(__OPTIMIZE__)
pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" :
#endif
PA_ALSA_PROFILE_SETS_DIR);
r = pa_config_parse(fn, NULL, items, ps);
pa_xfree(fn);
@ -3135,7 +3145,13 @@ fail:
return NULL;
}
void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss) {
void pa_alsa_profile_set_probe(
pa_alsa_profile_set *ps,
const char *dev_id,
const pa_sample_spec *ss,
unsigned default_n_fragments,
unsigned default_fragment_size_msec) {
void *state;
pa_alsa_profile *p, *last = NULL;
pa_alsa_mapping *m;
@ -3150,6 +3166,7 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
PA_HASHMAP_FOREACH(p, ps->profiles, state) {
pa_sample_spec try_ss;
pa_channel_map try_map;
snd_pcm_uframes_t try_period_size, try_buffer_size;
uint32_t idx;
/* Is this already marked that it is supported? (i.e. from the config file) */
@ -3203,13 +3220,18 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
try_ss = *ss;
try_ss.channels = try_map.channels;
try_period_size =
pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
pa_frame_size(&try_ss);
try_buffer_size = default_n_fragments * try_period_size;
if (!(m ->output_pcm = pa_alsa_open_by_template(
m->device_strings,
dev_id,
NULL,
&try_ss, &try_map,
SND_PCM_STREAM_PLAYBACK,
NULL, NULL, 0, NULL, NULL,
&try_period_size, &try_buffer_size, 0, NULL, NULL,
TRUE))) {
p->supported = FALSE;
break;
@ -3227,13 +3249,18 @@ void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, cons
try_ss = *ss;
try_ss.channels = try_map.channels;
try_period_size =
pa_usec_to_bytes(default_fragment_size_msec*PA_USEC_PER_MSEC, &try_ss) /
pa_frame_size(&try_ss);
try_buffer_size = default_n_fragments * try_period_size;
if (!(m ->input_pcm = pa_alsa_open_by_template(
m->device_strings,
dev_id,
NULL,
&try_ss, &try_map,
SND_PCM_STREAM_CAPTURE,
NULL, NULL, 0, NULL, NULL,
&try_period_size, &try_buffer_size, 0, NULL, NULL,
TRUE))) {
p->supported = FALSE;
break;

View file

@ -269,7 +269,7 @@ void pa_alsa_mapping_dump(pa_alsa_mapping *m);
void pa_alsa_profile_dump(pa_alsa_profile *p);
pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus);
void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss);
void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec);
void pa_alsa_profile_set_free(pa_alsa_profile_set *s);
void pa_alsa_profile_set_dump(pa_alsa_profile_set *s);

View file

@ -62,12 +62,27 @@
/* #define DEBUG_TIMING */
#define DEFAULT_DEVICE "default"
#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */
#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */
#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */
#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */
#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms -- When everything's great, decrease watermark by this */
#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s -- How long after a drop out recheck if things are good now */
#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms -- If the buffer level ever below this theshold, increase the watermark */
#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms -- If the buffer level didn't drop below this theshold in the verification time, decrease the watermark */
/* Note that TSCHED_WATERMARK_INC_THRESHOLD_USEC == 0 means tht we
* will increase the watermark only if we hit a real underrun. */
#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */
#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/
#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms -- min smoother update interval */
#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms -- max smoother update inteval */
#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */
struct userdata {
pa_core *core;
pa_module *module;
@ -94,9 +109,13 @@ struct userdata {
hwbuf_unused,
min_sleep,
min_wakeup,
watermark_step;
watermark_inc_step,
watermark_dec_step,
watermark_inc_threshold,
watermark_dec_threshold;
pa_usec_t watermark_dec_not_before;
unsigned nfragments;
pa_memchunk memchunk;
char *device_name; /* name of the PCM device */
@ -113,6 +132,8 @@ struct userdata {
pa_smoother *smoother;
uint64_t write_count;
uint64_t since_start;
pa_usec_t smoother_interval;
pa_usec_t last_smoother_update;
pa_reserve_wrapper *reserve;
pa_hook_slot *reserve_slot;
@ -241,6 +262,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
size_t max_use, max_use_2;
pa_assert(u);
pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
max_use_2 = pa_frame_align(max_use/2, &u->sink->sample_spec);
@ -255,6 +277,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
static void fix_tsched_watermark(struct userdata *u) {
size_t max_use;
pa_assert(u);
pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
@ -265,7 +288,7 @@ static void fix_tsched_watermark(struct userdata *u) {
u->tsched_watermark = u->min_wakeup;
}
static void adjust_after_underrun(struct userdata *u) {
static void increase_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t old_min_latency, new_min_latency;
@ -274,31 +297,64 @@ static void adjust_after_underrun(struct userdata *u) {
/* First, just try to increase the watermark */
old_watermark = u->tsched_watermark;
u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step);
u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
fix_tsched_watermark(u);
if (old_watermark != u->tsched_watermark) {
pa_log_notice("Increasing wakeup watermark to %0.2f ms",
pa_log_info("Increasing wakeup watermark to %0.2f ms",
(double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
return;
}
/* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
old_min_latency = u->sink->thread_info.min_latency;
new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC);
new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
new_min_latency = PA_MIN(new_min_latency, u->sink->thread_info.max_latency);
if (old_min_latency != new_min_latency) {
pa_log_notice("Increasing minimal latency to %0.2f ms",
pa_log_info("Increasing minimal latency to %0.2f ms",
(double) new_min_latency / PA_USEC_PER_MSEC);
pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency);
return;
}
/* When we reach this we're officialy fucked! */
}
static void decrease_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t now;
pa_assert(u);
pa_assert(u->use_tsched);
now = pa_rtclock_now();
if (u->watermark_dec_not_before <= 0)
goto restart;
if (u->watermark_dec_not_before > now)
return;
old_watermark = u->tsched_watermark;
if (u->tsched_watermark < u->watermark_dec_step)
u->tsched_watermark = u->tsched_watermark / 2;
else
u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
fix_tsched_watermark(u);
if (old_watermark != u->tsched_watermark)
pa_log_info("Decreasing wakeup watermark to %0.2f ms",
(double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
/* We don't change the latency range*/
restart:
u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
}
static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
pa_usec_t usec, wm;
@ -306,6 +362,7 @@ static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*p
pa_assert(process_usec);
pa_assert(u);
pa_assert(u->use_tsched);
usec = pa_sink_get_requested_latency_within_thread(u->sink);
@ -340,6 +397,9 @@ static int try_recover(struct userdata *u, const char *call, int err) {
if (err == -EPIPE)
pa_log_debug("%s: Buffer underrun!", call);
if (err == -ESTRPIPE)
pa_log_debug("%s: System suspended!", call);
if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) {
pa_log("%s: %s", call, pa_alsa_strerror(err));
return -1;
@ -350,42 +410,65 @@ static int try_recover(struct userdata *u, const char *call, int err) {
return 0;
}
static size_t check_left_to_play(struct userdata *u, size_t n_bytes) {
static size_t check_left_to_play(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
size_t left_to_play;
pa_bool_t underrun = FALSE;
/* We use <= instead of < for this check here because an underrun
* only happens after the last sample was processed, not already when
* it is removed from the buffer. This is particularly important
* when block transfer is used. */
if (n_bytes <= u->hwbuf_size) {
if (n_bytes <= u->hwbuf_size)
left_to_play = u->hwbuf_size - n_bytes;
else {
#ifdef DEBUG_TIMING
pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
#endif
} else {
/* We got a dropout. What a mess! */
left_to_play = 0;
underrun = TRUE;
#ifdef DEBUG_TIMING
PA_DEBUG_TRAP;
#endif
if (!u->first && !u->after_rewind) {
if (!u->first && !u->after_rewind)
if (pa_log_ratelimit())
pa_log_info("Underrun!");
if (u->use_tsched)
adjust_after_underrun(u);
}
#ifdef DEBUG_TIMING
pa_log_debug("%0.2f ms left to play; inc threshold = %0.2f ms; dec threshold = %0.2f ms",
(double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
(double) pa_bytes_to_usec(u->watermark_inc_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
(double) pa_bytes_to_usec(u->watermark_dec_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
#endif
if (u->use_tsched) {
pa_bool_t reset_not_before = TRUE;
if (!u->first && !u->after_rewind) {
if (underrun || left_to_play < u->watermark_inc_threshold)
increase_watermark(u);
else if (left_to_play > u->watermark_dec_threshold) {
reset_not_before = FALSE;
/* We decrease the watermark only if have actually
* been woken up by a timeout. If something else woke
* us up it's too easy to fulfill the deadlines... */
if (on_timeout)
decrease_watermark(u);
}
}
if (reset_not_before)
u->watermark_dec_not_before = 0;
}
return left_to_play;
}
static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = TRUE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_play;
@ -401,6 +484,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
snd_pcm_sframes_t n;
size_t n_bytes;
int r;
pa_bool_t after_avail = TRUE;
/* First we determine how many samples are missing to fill the
* buffer up to 100% */
@ -419,7 +503,8 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
pa_log_debug("avail: %lu", (unsigned long) n_bytes);
#endif
left_to_play = check_left_to_play(u, n_bytes);
left_to_play = check_left_to_play(u, n_bytes, on_timeout);
on_timeout = FALSE;
if (u->use_tsched)
@ -484,6 +569,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
if (!after_avail && err == -EAGAIN)
break;
if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
continue;
@ -494,6 +582,12 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size)
frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size;
if (!after_avail && frames == 0)
break;
pa_assert(frames > 0);
after_avail = FALSE;
/* Check these are multiples of 8 bit */
pa_assert((areas[0].first & 7) == 0);
pa_assert((areas[0].step & 7)== 0);
@ -545,7 +639,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
return work_done ? 1 : 0;
}
static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_play;
@ -561,6 +655,7 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
snd_pcm_sframes_t n;
size_t n_bytes;
int r;
pa_bool_t after_avail = TRUE;
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
@ -571,7 +666,8 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
}
n_bytes = (size_t) n * u->frame_size;
left_to_play = check_left_to_play(u, n_bytes);
left_to_play = check_left_to_play(u, n_bytes, on_timeout);
on_timeout = FALSE;
if (u->use_tsched)
@ -631,16 +727,23 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, (snd_pcm_uframes_t) frames);
pa_memblock_release(u->memchunk.memblock);
pa_assert(frames != 0);
if (PA_UNLIKELY(frames < 0)) {
if (!after_avail && (int) frames == -EAGAIN)
break;
if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0)
continue;
return r;
}
if (!after_avail && frames == 0)
break;
pa_assert(frames > 0);
after_avail = FALSE;
u->memchunk.index += (size_t) frames * u->frame_size;
u->memchunk.length -= (size_t) frames * u->frame_size;
@ -700,18 +803,27 @@ static void update_smoother(struct userdata *u) {
now1 = pa_timespec_load(&htstamp);
}
/* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
if (now1 <= 0)
now1 = pa_rtclock_now();
/* check if the time since the last update is bigger than the interval */
if (u->last_smoother_update > 0)
if (u->last_smoother_update + u->smoother_interval > now1)
return;
position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) u->frame_size);
if (PA_UNLIKELY(position < 0))
position = 0;
/* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
if (now1 <= 0)
now1 = pa_rtclock_now();
now2 = pa_bytes_to_usec((uint64_t) position, &u->sink->sample_spec);
pa_smoother_put(u->smoother, now1, now2);
u->last_smoother_update = now1;
/* exponentially increase the update interval up to the MAX limit */
u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
}
static pa_usec_t sink_get_latency(struct userdata *u) {
@ -830,8 +942,7 @@ static int unsuspend(struct userdata *u) {
pa_sample_spec ss;
int err;
pa_bool_t b, d;
unsigned nfrags;
snd_pcm_uframes_t period_size;
snd_pcm_uframes_t period_size, buffer_size;
pa_assert(u);
pa_assert(!u->pcm_handle);
@ -839,7 +950,7 @@ static int unsuspend(struct userdata *u) {
pa_log_info("Trying resume...");
if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK,
/*SND_PCM_NONBLOCK|*/
SND_PCM_NONBLOCK|
SND_PCM_NO_AUTO_RESAMPLE|
SND_PCM_NO_AUTO_CHANNELS|
SND_PCM_NO_AUTO_FORMAT)) < 0) {
@ -848,12 +959,12 @@ static int unsuspend(struct userdata *u) {
}
ss = u->sink->sample_spec;
nfrags = u->nfragments;
period_size = u->fragment_size / u->frame_size;
buffer_size = u->hwbuf_size / u->frame_size;
b = u->use_mmap;
d = u->use_tsched;
if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) {
if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) {
pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));
goto fail;
}
@ -868,10 +979,11 @@ static int unsuspend(struct userdata *u) {
goto fail;
}
if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) {
pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)",
(unsigned long) u->nfragments, (unsigned long) u->fragment_size,
(unsigned long) nfrags, period_size * u->frame_size);
if (period_size*u->frame_size != u->fragment_size ||
buffer_size*u->frame_size != u->hwbuf_size) {
pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)",
(unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size,
(unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size));
goto fail;
}
@ -881,6 +993,11 @@ static int unsuspend(struct userdata *u) {
if (build_pollfd(u) < 0)
goto fail;
u->write_count = 0;
pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
u->smoother_interval = SMOOTHER_MIN_INTERVAL;
u->last_smoother_update = 0;
u->first = TRUE;
u->since_start = 0;
@ -894,7 +1011,7 @@ fail:
u->pcm_handle = NULL;
}
return -1;
return -PA_ERR_IO;
}
/* Called from IO context */
@ -918,28 +1035,33 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
case PA_SINK_SUSPENDED:
case PA_SINK_SUSPENDED: {
int r;
pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
if (suspend(u) < 0)
return -1;
if ((r = suspend(u)) < 0)
return r;
break;
}
case PA_SINK_IDLE:
case PA_SINK_RUNNING:
case PA_SINK_RUNNING: {
int r;
if (u->sink->thread_info.state == PA_SINK_INIT) {
if (build_pollfd(u) < 0)
return -1;
return -PA_ERR_IO;
}
if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
if (unsuspend(u) < 0)
return -1;
if ((r = unsuspend(u)) < 0)
return r;
}
break;
}
case PA_SINK_UNLINKED:
case PA_SINK_INIT:
@ -967,7 +1089,7 @@ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t new_state) {
reserve_done(u);
else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state))
if (reserve_init(u, u->device_name) < 0)
return -1;
return -PA_ERR_BUSY;
return 0;
}
@ -982,7 +1104,7 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
return 0;
if (mask & SND_CTL_EVENT_MASK_VALUE) {
pa_sink_get_volume(u->sink, TRUE, FALSE);
pa_sink_get_volume(u->sink, TRUE);
pa_sink_get_mute(u->sink, TRUE);
}
@ -1009,15 +1131,11 @@ static void sink_get_volume_cb(pa_sink *s) {
if (pa_cvolume_equal(&u->hardware_volume, &r))
return;
s->virtual_volume = u->hardware_volume = r;
if (u->mixer_path->has_dB) {
pa_cvolume reset;
s->real_volume = u->hardware_volume = r;
/* Hmm, so the hardware volume changed, let's reset our software volume */
pa_cvolume_reset(&reset, s->sample_spec.channels);
pa_sink_set_soft_volume(s, &reset);
}
if (u->mixer_path->has_dB)
pa_sink_set_soft_volume(s, NULL);
}
static void sink_set_volume_cb(pa_sink *s) {
@ -1030,7 +1148,7 @@ static void sink_set_volume_cb(pa_sink *s) {
pa_assert(u->mixer_handle);
/* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume);
pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
return;
@ -1041,13 +1159,26 @@ static void sink_set_volume_cb(pa_sink *s) {
u->hardware_volume = r;
if (u->mixer_path->has_dB) {
pa_cvolume new_soft_volume;
pa_bool_t accurate_enough;
/* Match exactly what the user requested by software */
pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);
pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume);
pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
/* If the adjustment to do in software is only minimal we
* can skip it. That saves us CPU at the expense of a bit of
* accuracy */
accurate_enough =
(pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
(pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume));
pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume));
pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume));
pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume),
pa_yes_no(accurate_enough));
if (!accurate_enough)
s->soft_volume = new_soft_volume;
} else {
pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
@ -1055,7 +1186,7 @@ static void sink_set_volume_cb(pa_sink *s) {
/* We can't match exactly what the user requested, hence let's
* at least tell the user about it */
s->virtual_volume = r;
s->real_volume = r;
}
}
@ -1177,8 +1308,11 @@ static int process_rewind(struct userdata *u) {
pa_log_debug("before: %lu", (unsigned long) in_frames);
if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) {
pa_log("snd_pcm_rewind() failed: %s", pa_alsa_strerror((int) out_frames));
if (try_recover(u, "process_rewind", out_frames) < 0)
return -1;
out_frames = 0;
}
pa_log_debug("after: %lu", (unsigned long) out_frames);
rewind_nbytes = (size_t) out_frames * u->frame_size;
@ -1186,7 +1320,7 @@ static int process_rewind(struct userdata *u) {
if (rewind_nbytes <= 0)
pa_log_info("Tried rewind, but was apparently not possible.");
else {
u->write_count -= out_frames * u->frame_size;
u->write_count -= rewind_nbytes;
pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes);
pa_sink_process_rewind(u->sink, rewind_nbytes);
@ -1224,15 +1358,16 @@ static void thread_func(void *userdata) {
if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
int work_done;
pa_usec_t sleep_usec = 0;
pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
if (process_rewind(u) < 0)
goto fail;
if (u->use_mmap)
work_done = mmap_write(u, &sleep_usec, revents & POLLOUT);
work_done = mmap_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
else
work_done = unix_write(u, &sleep_usec, revents & POLLOUT);
work_done = unix_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
if (work_done < 0)
goto fail;
@ -1264,6 +1399,7 @@ static void thread_func(void *userdata) {
* we have filled the buffer at least once
* completely.*/
if (pa_log_ratelimit())
pa_log_debug("Cutting sleep time for the initial iterations by half.");
sleep_usec /= 2;
}
@ -1506,8 +1642,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
const char *dev_id = NULL;
pa_sample_spec ss, requested_ss;
pa_channel_map map;
uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark;
snd_pcm_uframes_t period_frames, tsched_frames;
uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark;
snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames;
size_t frame_size;
pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;
pa_sink_new_data data;
@ -1541,8 +1677,10 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
goto fail;
}
hwbuf_size = frag_size * nfrags;
buffer_size = nfrags * frag_size;
period_frames = frag_size/frame_size;
buffer_frames = buffer_size/frame_size;
tsched_frames = tsched_size/frame_size;
if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
@ -1582,6 +1720,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
5,
pa_rtclock_now(),
TRUE);
u->smoother_interval = SMOOTHER_MIN_INTERVAL;
dev_id = pa_modargs_get_value(
ma, "device_id",
@ -1608,7 +1747,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
&u->device_name,
&ss, &map,
SND_PCM_STREAM_PLAYBACK,
&nfrags, &period_frames, tsched_frames,
&period_frames, &buffer_frames, tsched_frames,
&b, &d, mapping)))
goto fail;
@ -1623,7 +1762,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
&u->device_name,
&ss, &map,
SND_PCM_STREAM_PLAYBACK,
&nfrags, &period_frames, tsched_frames,
&period_frames, &buffer_frames, tsched_frames,
&b, &d, profile_set, &mapping)))
goto fail;
@ -1635,7 +1774,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
&u->device_name,
&ss, &map,
SND_PCM_STREAM_PLAYBACK,
&nfrags, &period_frames, tsched_frames,
&period_frames, &buffer_frames, tsched_frames,
&b, &d, FALSE)))
goto fail;
}
@ -1661,11 +1800,6 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
u->use_tsched = use_tsched = FALSE;
}
if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) {
pa_log_info("Device is not a hardware device, disabling timer-based scheduling.");
u->use_tsched = use_tsched = FALSE;
}
if (u->use_mmap)
pa_log_info("Successfully enabled mmap() mode.");
@ -1687,7 +1821,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size));
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial"));
@ -1728,21 +1862,28 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
pa_sink_set_rtpoll(u->sink, u->rtpoll);
u->frame_size = frame_size;
u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size);
u->nfragments = nfrags;
u->hwbuf_size = u->fragment_size * nfrags;
u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);
u->fragment_size = frag_size = (size_t) (period_frames * frame_size);
u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size);
pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels);
pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms",
nfrags, (long unsigned) u->fragment_size,
pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)",
(double) u->hwbuf_size / (double) u->fragment_size,
(long unsigned) u->fragment_size,
(double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC,
(long unsigned) u->hwbuf_size,
(double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
pa_sink_set_max_request(u->sink, u->hwbuf_size);
pa_sink_set_max_rewind(u->sink, u->hwbuf_size);
if (u->use_tsched) {
u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->sink->sample_spec);
u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);
u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->sink->sample_spec);
u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->sink->sample_spec);
u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->sink->sample_spec);
u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->sink->sample_spec);
fix_min_sleep_wakeup(u);
fix_tsched_watermark(u);
@ -1756,6 +1897,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
} else
pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss));
reserve_update(u);
if (update_sw_params(u) < 0)

View file

@ -59,12 +59,25 @@
/* #define DEBUG_TIMING */
#define DEFAULT_DEVICE "default"
#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */
#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */
#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms */
#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s */
#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms */
#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms */
#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */
#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms */
#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms */
#define VOLUME_ACCURACY (PA_VOLUME_NORM/100)
struct userdata {
pa_core *core;
pa_module *module;
@ -91,9 +104,12 @@ struct userdata {
hwbuf_unused,
min_sleep,
min_wakeup,
watermark_step;
watermark_inc_step,
watermark_dec_step,
watermark_inc_threshold,
watermark_dec_threshold;
unsigned nfragments;
pa_usec_t watermark_dec_not_before;
char *device_name;
char *control_device;
@ -106,6 +122,8 @@ struct userdata {
pa_smoother *smoother;
uint64_t read_count;
pa_usec_t smoother_interval;
pa_usec_t last_smoother_update;
pa_reserve_wrapper *reserve;
pa_hook_slot *reserve_slot;
@ -234,6 +252,7 @@ static int reserve_monitor_init(struct userdata *u, const char *dname) {
static void fix_min_sleep_wakeup(struct userdata *u) {
size_t max_use, max_use_2;
pa_assert(u);
pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
max_use_2 = pa_frame_align(max_use/2, &u->source->sample_spec);
@ -248,6 +267,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
static void fix_tsched_watermark(struct userdata *u) {
size_t max_use;
pa_assert(u);
pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
@ -258,7 +278,7 @@ static void fix_tsched_watermark(struct userdata *u) {
u->tsched_watermark = u->min_wakeup;
}
static void adjust_after_overrun(struct userdata *u) {
static void increase_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t old_min_latency, new_min_latency;
@ -267,36 +287,72 @@ static void adjust_after_overrun(struct userdata *u) {
/* First, just try to increase the watermark */
old_watermark = u->tsched_watermark;
u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step);
u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
fix_tsched_watermark(u);
if (old_watermark != u->tsched_watermark) {
pa_log_notice("Increasing wakeup watermark to %0.2f ms",
pa_log_info("Increasing wakeup watermark to %0.2f ms",
(double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
return;
}
/* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
old_min_latency = u->source->thread_info.min_latency;
new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC);
new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency);
if (old_min_latency != new_min_latency) {
pa_log_notice("Increasing minimal latency to %0.2f ms",
pa_log_info("Increasing minimal latency to %0.2f ms",
(double) new_min_latency / PA_USEC_PER_MSEC);
pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency);
return;
}
/* When we reach this we're officialy fucked! */
}
static void decrease_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t now;
pa_assert(u);
pa_assert(u->use_tsched);
now = pa_rtclock_now();
if (u->watermark_dec_not_before <= 0)
goto restart;
if (u->watermark_dec_not_before > now)
return;
old_watermark = u->tsched_watermark;
if (u->tsched_watermark < u->watermark_dec_step)
u->tsched_watermark = u->tsched_watermark / 2;
else
u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
fix_tsched_watermark(u);
if (old_watermark != u->tsched_watermark)
pa_log_info("Decreasing wakeup watermark to %0.2f ms",
(double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
/* We don't change the latency range*/
restart:
u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
}
static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
pa_usec_t wm, usec;
pa_assert(sleep_usec);
pa_assert(process_usec);
pa_assert(u);
pa_assert(u->use_tsched);
usec = pa_source_get_requested_latency_within_thread(u->source);
@ -333,6 +389,9 @@ static int try_recover(struct userdata *u, const char *call, int err) {
if (err == -EPIPE)
pa_log_debug("%s: Buffer overrun!", call);
if (err == -ESTRPIPE)
pa_log_debug("%s: System suspended!", call);
if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) {
pa_log("%s: %s", call, pa_alsa_strerror(err));
return -1;
@ -342,24 +401,23 @@ static int try_recover(struct userdata *u, const char *call, int err) {
return 0;
}
static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
static size_t check_left_to_record(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
size_t left_to_record;
size_t rec_space = u->hwbuf_size - u->hwbuf_unused;
pa_bool_t overrun = FALSE;
/* We use <= instead of < for this check here because an overrun
* only happens after the last sample was processed, not already when
* it is removed from the buffer. This is particularly important
* when block transfer is used. */
if (n_bytes <= rec_space) {
if (n_bytes <= rec_space)
left_to_record = rec_space - n_bytes;
else {
#ifdef DEBUG_TIMING
pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC);
#endif
} else {
/* We got a dropout. What a mess! */
left_to_record = 0;
overrun = TRUE;
#ifdef DEBUG_TIMING
PA_DEBUG_TRAP;
@ -367,15 +425,36 @@ static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
if (pa_log_ratelimit())
pa_log_info("Overrun!");
}
if (u->use_tsched)
adjust_after_overrun(u);
#ifdef DEBUG_TIMING
pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC);
#endif
if (u->use_tsched) {
pa_bool_t reset_not_before = TRUE;
if (overrun || left_to_record < u->watermark_inc_threshold)
increase_watermark(u);
else if (left_to_record > u->watermark_dec_threshold) {
reset_not_before = FALSE;
/* We decrease the watermark only if have actually been
* woken up by a timeout. If something else woke us up
* it's too easy to fulfill the deadlines... */
if (on_timeout)
decrease_watermark(u);
}
if (reset_not_before)
u->watermark_dec_not_before = 0;
}
return left_to_record;
}
static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
@ -391,6 +470,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
snd_pcm_sframes_t n;
size_t n_bytes;
int r;
pa_bool_t after_avail = TRUE;
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
@ -406,7 +486,8 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
pa_log_debug("avail: %lu", (unsigned long) n_bytes);
#endif
left_to_record = check_left_to_record(u, n_bytes);
left_to_record = check_left_to_record(u, n_bytes, on_timeout);
on_timeout = FALSE;
if (u->use_tsched)
if (!polled &&
@ -463,6 +544,9 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
if (!after_avail && err == -EAGAIN)
break;
if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
continue;
@ -473,6 +557,12 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size)
frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size;
if (!after_avail && frames == 0)
break;
pa_assert(frames > 0);
after_avail = FALSE;
/* Check these are multiples of 8 bit */
pa_assert((areas[0].first & 7) == 0);
pa_assert((areas[0].step & 7)== 0);
@ -523,7 +613,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
return work_done ? 1 : 0;
}
static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
int work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
@ -539,6 +629,7 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
snd_pcm_sframes_t n;
size_t n_bytes;
int r;
pa_bool_t after_avail = TRUE;
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
@ -549,7 +640,8 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
}
n_bytes = (size_t) n * u->frame_size;
left_to_record = check_left_to_record(u, n_bytes);
left_to_record = check_left_to_record(u, n_bytes, on_timeout);
on_timeout = FALSE;
if (u->use_tsched)
if (!polled &&
@ -599,17 +691,26 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, (snd_pcm_uframes_t) frames);
pa_memblock_release(chunk.memblock);
pa_assert(frames != 0);
if (PA_UNLIKELY(frames < 0)) {
pa_memblock_unref(chunk.memblock);
if ((r = try_recover(u, "snd_pcm_readi", (int) (frames))) == 0)
if (!after_avail && (int) frames == -EAGAIN)
break;
if ((r = try_recover(u, "snd_pcm_readi", (int) frames)) == 0)
continue;
return r;
}
if (!after_avail && frames == 0) {
pa_memblock_unref(chunk.memblock);
break;
}
pa_assert(frames > 0);
after_avail = FALSE;
chunk.index = 0;
chunk.length = (size_t) frames * u->frame_size;
@ -666,15 +767,23 @@ static void update_smoother(struct userdata *u) {
now1 = pa_timespec_load(&htstamp);
}
position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
/* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
if (now1 <= 0)
now1 = pa_rtclock_now();
/* check if the time since the last update is bigger than the interval */
if (u->last_smoother_update > 0)
if (u->last_smoother_update + u->smoother_interval > now1)
return;
position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
now2 = pa_bytes_to_usec(position, &u->source->sample_spec);
pa_smoother_put(u->smoother, now1, now2);
u->last_smoother_update = now1;
/* exponentially increase the update interval up to the MAX limit */
u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
}
static pa_usec_t source_get_latency(struct userdata *u) {
@ -780,8 +889,7 @@ static int unsuspend(struct userdata *u) {
pa_sample_spec ss;
int err;
pa_bool_t b, d;
unsigned nfrags;
snd_pcm_uframes_t period_size;
snd_pcm_uframes_t period_size, buffer_size;
pa_assert(u);
pa_assert(!u->pcm_handle);
@ -789,7 +897,7 @@ static int unsuspend(struct userdata *u) {
pa_log_info("Trying resume...");
if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE,
/*SND_PCM_NONBLOCK|*/
SND_PCM_NONBLOCK|
SND_PCM_NO_AUTO_RESAMPLE|
SND_PCM_NO_AUTO_CHANNELS|
SND_PCM_NO_AUTO_FORMAT)) < 0) {
@ -798,12 +906,12 @@ static int unsuspend(struct userdata *u) {
}
ss = u->source->sample_spec;
nfrags = u->nfragments;
period_size = u->fragment_size / u->frame_size;
buffer_size = u->hwbuf_size / u->frame_size;
b = u->use_mmap;
d = u->use_tsched;
if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) {
if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) {
pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));
goto fail;
}
@ -818,10 +926,11 @@ static int unsuspend(struct userdata *u) {
goto fail;
}
if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) {
pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)",
(unsigned long) u->nfragments, (unsigned long) u->fragment_size,
(unsigned long) nfrags, period_size * u->frame_size);
if (period_size*u->frame_size != u->fragment_size ||
buffer_size*u->frame_size != u->hwbuf_size) {
pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)",
(unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size,
(unsigned long) (buffer_size*u->fragment_size), (unsigned long) (period_size*u->frame_size));
goto fail;
}
@ -834,7 +943,11 @@ static int unsuspend(struct userdata *u) {
/* FIXME: We need to reload the volume somehow */
snd_pcm_start(u->pcm_handle);
pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);
u->read_count = 0;
pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
u->smoother_interval = SMOOTHER_MIN_INTERVAL;
u->last_smoother_update = 0;
pa_log_info("Resumed successfully...");
@ -846,7 +959,7 @@ fail:
u->pcm_handle = NULL;
}
return -1;
return -PA_ERR_IO;
}
static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
@ -869,30 +982,34 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
case PA_SOURCE_SUSPENDED:
case PA_SOURCE_SUSPENDED: {
int r;
pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));
if (suspend(u) < 0)
return -1;
if ((r = suspend(u)) < 0)
return r;
break;
}
case PA_SOURCE_IDLE:
case PA_SOURCE_RUNNING:
case PA_SOURCE_RUNNING: {
int r;
if (u->source->thread_info.state == PA_SOURCE_INIT) {
if (build_pollfd(u) < 0)
return -1;
return -PA_ERR_IO;
snd_pcm_start(u->pcm_handle);
}
if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) {
if (unsuspend(u) < 0)
return -1;
if ((r = unsuspend(u)) < 0)
return r;
}
break;
}
case PA_SOURCE_UNLINKED:
case PA_SOURCE_INIT:
@ -920,7 +1037,7 @@ static int source_set_state_cb(pa_source *s, pa_source_state_t new_state) {
reserve_done(u);
else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state))
if (reserve_init(u, u->device_name) < 0)
return -1;
return -PA_ERR_BUSY;
return 0;
}
@ -962,15 +1079,11 @@ static void source_get_volume_cb(pa_source *s) {
if (pa_cvolume_equal(&u->hardware_volume, &r))
return;
s->virtual_volume = u->hardware_volume = r;
if (u->mixer_path->has_dB) {
pa_cvolume reset;
s->volume = u->hardware_volume = r;
/* Hmm, so the hardware volume changed, let's reset our software volume */
pa_cvolume_reset(&reset, s->sample_spec.channels);
pa_source_set_soft_volume(s, &reset);
}
if (u->mixer_path->has_dB)
pa_source_set_soft_volume(s, NULL);
}
static void source_set_volume_cb(pa_source *s) {
@ -983,7 +1096,7 @@ static void source_set_volume_cb(pa_source *s) {
pa_assert(u->mixer_handle);
/* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume);
pa_sw_cvolume_divide_scalar(&r, &s->volume, s->base_volume);
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
return;
@ -994,13 +1107,26 @@ static void source_set_volume_cb(pa_source *s) {
u->hardware_volume = r;
if (u->mixer_path->has_dB) {
pa_cvolume new_soft_volume;
pa_bool_t accurate_enough;
/* Match exactly what the user requested by software */
pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);
pa_sw_cvolume_divide(&new_soft_volume, &s->volume, &u->hardware_volume);
pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
/* If the adjustment to do in software is only minimal we
* can skip it. That saves us CPU at the expense of a bit of
* accuracy */
accurate_enough =
(pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
(pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume));
pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume));
pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume));
pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume),
pa_yes_no(accurate_enough));
if (!accurate_enough)
s->soft_volume = new_soft_volume;
} else {
pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
@ -1008,7 +1134,7 @@ static void source_set_volume_cb(pa_source *s) {
/* We can't match exactly what the user requested, hence let's
* at least tell the user about it */
s->virtual_volume = r;
s->volume = r;
}
}
@ -1107,11 +1233,12 @@ static void thread_func(void *userdata) {
if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
int work_done;
pa_usec_t sleep_usec = 0;
pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
if (u->use_mmap)
work_done = mmap_read(u, &sleep_usec, revents & POLLIN);
work_done = mmap_read(u, &sleep_usec, revents & POLLIN, on_timeout);
else
work_done = unix_read(u, &sleep_usec, revents & POLLIN);
work_done = unix_read(u, &sleep_usec, revents & POLLIN, on_timeout);
if (work_done < 0)
goto fail;
@ -1358,8 +1485,8 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
const char *dev_id = NULL;
pa_sample_spec ss, requested_ss;
pa_channel_map map;
uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark;
snd_pcm_uframes_t period_frames, tsched_frames;
uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark;
snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames;
size_t frame_size;
pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;
pa_source_new_data data;
@ -1393,8 +1520,10 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
goto fail;
}
hwbuf_size = frag_size * nfrags;
buffer_size = nfrags * frag_size;
period_frames = frag_size/frame_size;
buffer_frames = buffer_size/frame_size;
tsched_frames = tsched_size/frame_size;
if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
@ -1433,6 +1562,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
5,
pa_rtclock_now(),
FALSE);
u->smoother_interval = SMOOTHER_MIN_INTERVAL;
dev_id = pa_modargs_get_value(
ma, "device_id",
@ -1459,7 +1589,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
&nfrags, &period_frames, tsched_frames,
&period_frames, &buffer_frames, tsched_frames,
&b, &d, mapping)))
goto fail;
@ -1473,7 +1603,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
&nfrags, &period_frames, tsched_frames,
&period_frames, &buffer_frames, tsched_frames,
&b, &d, profile_set, &mapping)))
goto fail;
@ -1484,7 +1614,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
&nfrags, &period_frames, tsched_frames,
&period_frames, &buffer_frames, tsched_frames,
&b, &d, FALSE)))
goto fail;
}
@ -1510,11 +1640,6 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
u->use_tsched = use_tsched = FALSE;
}
if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) {
pa_log_info("Device is not a hardware device, disabling timer-based scheduling.");
u->use_tsched = use_tsched = FALSE;
}
if (u->use_mmap)
pa_log_info("Successfully enabled mmap() mode.");
@ -1536,7 +1661,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size));
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial"));
@ -1577,18 +1702,25 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
pa_source_set_rtpoll(u->source, u->rtpoll);
u->frame_size = frame_size;
u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size);
u->nfragments = nfrags;
u->hwbuf_size = u->fragment_size * nfrags;
u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
u->fragment_size = frag_size = (size_t) (period_frames * frame_size);
u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size);
pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels);
pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms",
nfrags, (long unsigned) u->fragment_size,
pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)",
(double) u->hwbuf_size / (double) u->fragment_size,
(long unsigned) u->fragment_size,
(double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC,
(long unsigned) u->hwbuf_size,
(double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
if (u->use_tsched) {
u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->source->sample_spec);
u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->source->sample_spec);
u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->source->sample_spec);
u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->source->sample_spec);
u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->source->sample_spec);
fix_min_sleep_wakeup(u);
fix_tsched_watermark(u);

View file

@ -93,6 +93,7 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s
int ret;
pa_assert(pcm_handle);
pa_assert(hwparams);
pa_assert(f);
if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
@ -148,33 +149,71 @@ try_auto:
return -1;
}
static int set_period_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) {
snd_pcm_uframes_t s;
int d, ret;
pa_assert(pcm_handle);
pa_assert(hwparams);
s = size;
d = 0;
if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) {
s = size;
d = -1;
if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) {
s = size;
d = 1;
if ((ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d)) < 0) {
pa_log_info("snd_pcm_hw_params_set_period_size_near() failed: %s", pa_alsa_strerror(ret));
return ret;
}
}
}
return 0;
}
static int set_buffer_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) {
int ret;
pa_assert(pcm_handle);
pa_assert(hwparams);
if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &size)) < 0) {
pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret));
return ret;
}
return 0;
}
/* Set the hardware parameters of the given ALSA device. Returns the
* selected fragment settings in *period and *period_size */
* selected fragment settings in *buffer_size and *period_size. If tsched mode can be enabled */
int pa_alsa_set_hw_params(
snd_pcm_t *pcm_handle,
pa_sample_spec *ss,
uint32_t *periods,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
pa_bool_t require_exact_channel_number) {
int ret = -1;
snd_pcm_hw_params_t *hwparams, *hwparams_copy;
int dir;
snd_pcm_uframes_t _period_size = period_size ? *period_size : 0;
unsigned int _periods = periods ? *periods : 0;
unsigned int r = ss->rate;
unsigned int c = ss->channels;
pa_sample_format_t f = ss->format;
snd_pcm_hw_params_t *hwparams;
snd_pcm_uframes_t _buffer_size = buffer_size ? *buffer_size : 0;
pa_bool_t _use_mmap = use_mmap && *use_mmap;
pa_bool_t _use_tsched = use_tsched && *use_tsched;
int dir;
pa_sample_spec _ss = *ss;
pa_assert(pcm_handle);
pa_assert(ss);
snd_pcm_hw_params_alloca(&hwparams);
snd_pcm_hw_params_alloca(&hwparams_copy);
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));
@ -208,111 +247,143 @@ int pa_alsa_set_hw_params(
if (!_use_mmap)
_use_tsched = FALSE;
if ((ret = set_format(pcm_handle, hwparams, &f)) < 0)
if (!pa_alsa_pcm_is_hw(pcm_handle))
_use_tsched = FALSE;
if ((ret = set_format(pcm_handle, hwparams, &_ss.format)) < 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, &_ss.rate, 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) {
pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", c, pa_alsa_strerror(ret));
if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, _ss.channels)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret));
goto finish;
}
} else {
unsigned int c = _ss.channels;
if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", c, pa_alsa_strerror(ret));
goto finish;
}
}
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));
pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret));
goto finish;
}
if (_period_size && tsched_size && _periods) {
_ss.channels = c;
}
/* 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 && tsched_size > 0) {
_buffer_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * _ss.rate) / ss->rate);
_period_size = _buffer_size;
} else {
_period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * _ss.rate) / ss->rate);
_buffer_size = (snd_pcm_uframes_t) (((uint64_t) _buffer_size * _ss.rate) / ss->rate);
}
if (_use_tsched) {
snd_pcm_uframes_t buffer_size = 0;
if (_buffer_size > 0 || _period_size > 0) {
snd_pcm_uframes_t max_frames = 0;
if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size)) < 0)
if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0)
pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret));
else
pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r);
pa_log_debug("Maximum hw buffer size is %lu ms", (long unsigned) max_frames * PA_MSEC_PER_SEC / _ss.rate);
_period_size = tsched_size;
_periods = 1;
/* Some ALSA drivers really don't like if we set the buffer
* size first and the number of periods second. (which would
* make a lot more sense to me) So, try a few combinations
* before we give up. */
if (_buffer_size > 0 && _period_size > 0) {
snd_pcm_hw_params_copy(hwparams_copy, hwparams);
/* First try: set buffer size first, followed by period size */
if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
pa_log_debug("Set buffer size first, period size second.");
goto success;
}
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. All in all this is mostly a hint to ALSA, so
* we don't care if this fails. */
dir = 0;
if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir) < 0) {
dir = 1;
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)
pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret));
}
}
/* Second try: set period size first, followed by buffer size */
if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
pa_log_debug("Set period size first, buffer size second.");
goto success;
}
}
if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0)
if (_buffer_size > 0) {
snd_pcm_hw_params_copy(hwparams_copy, hwparams);
/* Third try: set only buffer size */
if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
pa_log_debug("Set only buffer size second.");
goto success;
}
}
if (_period_size > 0) {
snd_pcm_hw_params_copy(hwparams_copy, hwparams);
/* Fourth try: set only period size */
if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
pa_log_debug("Set only period size second.");
goto success;
}
}
}
pa_log_debug("Set neither period nor buffer size.");
/* Last chance, set nothing */
if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
pa_log_info("snd_pcm_hw_params failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if (ss->rate != r)
pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r);
success:
if (ss->channels != c)
pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c);
if (ss->rate != _ss.rate)
pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, _ss.rate);
if (ss->format != f)
pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f));
if (ss->channels != _ss.channels)
pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, _ss.channels);
if (ss->format != _ss.format)
pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(_ss.format));
if ((ret = snd_pcm_prepare(pcm_handle)) < 0) {
pa_log_info("snd_pcm_prepare() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if ((ret = snd_pcm_hw_params_current(pcm_handle, hwparams)) < 0) {
pa_log_info("snd_pcm_hw_params_current() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 ||
(ret = snd_pcm_hw_params_get_periods(hwparams, &_periods, &dir)) < 0) {
pa_log_info("snd_pcm_hw_params_get_period{s|_size}() failed: %s", pa_alsa_strerror(ret));
(ret = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0) {
pa_log_info("snd_pcm_hw_params_get_{period|buffer}_size() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
/* If the sample rate deviates too much, we need to resample */
if (r < ss->rate*.95 || r > ss->rate*1.05)
ss->rate = r;
ss->channels = (uint8_t) c;
ss->format = f;
if (_ss.rate < ss->rate*.95 || _ss.rate > ss->rate*1.05)
ss->rate = _ss.rate;
ss->channels = _ss.channels;
ss->format = _ss.format;
pa_assert(_periods > 0);
pa_assert(_period_size > 0);
pa_assert(_buffer_size > 0);
if (periods)
*periods = _periods;
if (buffer_size)
*buffer_size = _buffer_size;
if (period_size)
*period_size = _period_size;
@ -390,8 +461,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@ -407,8 +478,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
pa_assert(dev);
pa_assert(ss);
pa_assert(map);
pa_assert(nfrags);
pa_assert(period_size);
pa_assert(ps);
/* First we try to find a device string with a superset of the
@ -430,8 +499,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
ss,
map,
mode,
nfrags,
period_size,
buffer_size,
tsched_size,
use_mmap,
use_tsched,
@ -457,8 +526,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
ss,
map,
mode,
nfrags,
period_size,
buffer_size,
tsched_size,
use_mmap,
use_tsched,
@ -475,7 +544,18 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
/* OK, we didn't find any good device, so let's try the raw hw: stuff */
d = pa_sprintf_malloc("hw:%s", dev_id);
pa_log_debug("Trying %s as last resort...", d);
pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE);
pcm_handle = pa_alsa_open_by_device_string(
d,
dev,
ss,
map,
mode,
period_size,
buffer_size,
tsched_size,
use_mmap,
use_tsched,
FALSE);
pa_xfree(d);
if (pcm_handle && mapping)
@ -490,8 +570,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@ -505,8 +585,6 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
pa_assert(dev);
pa_assert(ss);
pa_assert(map);
pa_assert(nfrags);
pa_assert(period_size);
pa_assert(m);
try_ss.channels = m->channel_map.channels;
@ -521,8 +599,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
&try_ss,
&try_map,
mode,
nfrags,
period_size,
buffer_size,
tsched_size,
use_mmap,
use_tsched,
@ -544,8 +622,8 @@ snd_pcm_t *pa_alsa_open_by_device_string(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@ -576,7 +654,15 @@ snd_pcm_t *pa_alsa_open_by_device_string(
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 ((err = pa_alsa_set_hw_params(
pcm_handle,
ss,
period_size,
buffer_size,
tsched_size,
use_mmap,
use_tsched,
require_exact_channel_number)) < 0) {
if (!reformat) {
reformat = TRUE;
@ -629,8 +715,8 @@ snd_pcm_t *pa_alsa_open_by_template(
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t *buffer_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
@ -650,8 +736,8 @@ snd_pcm_t *pa_alsa_open_by_template(
ss,
map,
mode,
nfrags,
period_size,
buffer_size,
tsched_size,
use_mmap,
use_tsched,
@ -897,7 +983,7 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) {
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);
pa_log_warn("Error opening low-level control device '%s': %s", name, snd_strerror(err));
return;
}

View file

@ -42,8 +42,8 @@
int pa_alsa_set_hw_params(
snd_pcm_t *pcm_handle,
pa_sample_spec *ss, /* modified at return */
uint32_t *periods, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@ -60,8 +60,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@ -75,8 +75,8 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@ -89,8 +89,8 @@ snd_pcm_t *pa_alsa_open_by_device_string(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */
@ -104,8 +104,8 @@ snd_pcm_t *pa_alsa_open_by_template(
pa_sample_spec *ss, /* modified at return */
pa_channel_map* map, /* modified at return */
int mode,
uint32_t *nfrags, /* modified at return */
snd_pcm_uframes_t *period_size, /* modified at return */
snd_pcm_uframes_t *buffer_size, /* modified at return */
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap, /* modified at return */
pa_bool_t *use_tsched, /* modified at return */

View file

@ -0,0 +1 @@
../../../pulse/Makefile

View file

@ -0,0 +1 @@
../../../../pulse/Makefile

View file

@ -78,6 +78,10 @@ priority = 19
name = input-microphone
priority = 19
[Option Input Source:Internal Mic]
name = input-microphone
priority = 19
[Option Input Source:Line]
name = input-linein
priority = 18
@ -90,6 +94,10 @@ priority = 18
name = input-linein
priority = 18
[Option Input Source:Docking-Station]
name = input-docking
priority = 17
;;; ' Capture Source'
[Element Capture Source]

View file

@ -44,6 +44,10 @@ volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
[Element Speaker]
switch = off
volume = off
[Element Front]
switch = off
volume = off

View file

@ -41,9 +41,18 @@ volume = merge
override-map.1 = lfe
override-map.2 = lfe,lfe
; This profile path is intended to control the speaker, not the
; headphones. But it should not hurt if we leave the headphone jack
; enabled nonetheless.
[Element Headphone]
switch = off
volume = off
switch = mute
volume = zero
[Element Speaker]
switch = mute
volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
[Element Front]
switch = off

View file

@ -38,9 +38,18 @@ volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
; This profile path is intended to control the speaker, not the
; headphones. But it should not hurt if we leave the headphone jack
; enabled nonetheless.
[Element Headphone]
switch = off
volume = off
switch = mute
volume = zero
[Element Speaker]
switch = mute
volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
[Element Front]
switch = off

View file

@ -37,9 +37,18 @@ override-map.2 = all-left,all-right
switch = off
volume = off
; This profile path is intended to control the speaker, not the
; headphones. But it should not hurt if we leave the headphone jack
; enabled nonetheless.
[Element Headphone]
switch = off
volume = off
switch = mute
volume = zero
[Element Speaker]
switch = mute
volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
[Element Front]
switch = mute

View file

@ -104,8 +104,8 @@ switch = select
[Option External Amplifier:on]
name = output-amplifier-on
priority = 0
priority = 10
[Option External Amplifier:off]
name = output-amplifier-off
priority = 10
priority = 0

View file

@ -0,0 +1 @@
../../../../pulse/Makefile

View file

@ -329,7 +329,7 @@ int pa__init(pa_module *m) {
if (!u->profile_set)
goto fail;
pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec);
pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec);
pa_card_new_data_init(&data);
data.driver = __FILE__;

View file

@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
Copyright 2008 Joao Paulo Rechi Vita
Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -95,6 +95,7 @@ static pa_bluetooth_device* device_new(const char *path) {
d->audio_state = PA_BT_AUDIO_STATE_INVALID;
d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
d->audio_source_state = PA_BT_AUDIO_STATE_INVALID;
d->headset_state = PA_BT_AUDIO_STATE_INVALID;
return d;
@ -122,9 +123,10 @@ static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
return
d->device_info_valid &&
(d->audio_state != PA_BT_AUDIO_STATE_INVALID ||
d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
d->headset_state != PA_BT_AUDIO_STATE_INVALID);
(d->audio_state != PA_BT_AUDIO_STATE_INVALID &&
(d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
d->audio_source_state != PA_BT_AUDIO_STATE_INVALID ||
d->headset_state != PA_BT_AUDIO_STATE_INVALID));
}
static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
@ -226,10 +228,6 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
node = uuid_new(value);
PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
/* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
/* Vudentz said the interfaces are here when the UUIDs are announced */
if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
@ -237,8 +235,15 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
} else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
} else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
}
/* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
if (!dbus_message_iter_next(&ai))
break;
}
@ -278,7 +283,7 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessa
dbus_message_iter_recurse(i, &variant_i);
/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%s", key); */
/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */
switch (dbus_message_iter_get_arg_type(&variant_i)) {
@ -390,6 +395,9 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
} else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
goto finish;
} else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) {
if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0)
goto finish;
}
}
@ -440,8 +448,8 @@ static void found_device(pa_bluetooth_discovery *y, const char* path) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
/* Before we read the other properties (Audio, AudioSink, Headset) we wait
* that the UUID is read */
/* Before we read the other properties (Audio, AudioSink, AudioSource,
* Headset) we wait that the UUID is read */
}
static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
@ -616,6 +624,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
} else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
pa_bluetooth_device *d;
@ -643,6 +652,9 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
} else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
goto fail;
} else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) {
if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0)
goto fail;
}
run_callback(y, d, FALSE);
@ -650,6 +662,21 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) {
pa_bluetooth_device *d;
if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) {
/* Device will disconnect in 2 sec */
d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED;
d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED;
d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED;
d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED;
run_callback(y, d, FALSE);
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
const char *name, *old_owner, *new_owner;
@ -758,14 +785,16 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
if (pa_dbus_add_matches(
pa_dbus_connection_get(y->connection), &err,
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'",
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
"type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL) < 0) {
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL) < 0) {
pa_log("Failed to add D-Bus matches: %s", err.message);
goto fail;
}
@ -809,15 +838,17 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
if (y->connection) {
pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'",
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'",
"type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL);
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", NULL);
dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);

View file

@ -4,7 +4,7 @@
/***
This file is part of PulseAudio.
Copyright 2008 Joao Paulo Rechi Vita
Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -53,14 +53,13 @@ struct pa_bluetooth_uuid {
PA_LLIST_FIELDS(pa_bluetooth_uuid);
};
/* This enum is shared among Audio, Headset, and AudioSink, although not all values are acceptable in all profiles */
/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */
typedef enum pa_bt_audio_state {
PA_BT_AUDIO_STATE_INVALID = -1,
PA_BT_AUDIO_STATE_DISCONNECTED,
PA_BT_AUDIO_STATE_CONNECTING,
PA_BT_AUDIO_STATE_CONNECTED,
PA_BT_AUDIO_STATE_PLAYING,
PA_BT_AUDIO_STATE_LAST
PA_BT_AUDIO_STATE_PLAYING
} pa_bt_audio_state_t;
struct pa_bluetooth_device {
@ -85,6 +84,9 @@ struct pa_bluetooth_device {
/* AudioSink state */
pa_bt_audio_state_t audio_sink_state;
/* AudioSource state */
pa_bt_audio_state_t audio_source_state;
/* Headset state */
pa_bt_audio_state_t headset_state;
};

View file

@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
Copyright 2008 Joao Paulo Rechi Vita
Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -129,6 +129,7 @@ struct hsp_info {
enum profile {
PROFILE_A2DP,
PROFILE_A2DP_SOURCE,
PROFILE_HSP,
PROFILE_OFF
};
@ -178,6 +179,7 @@ struct userdata {
};
#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC)
@ -219,9 +221,7 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r
pa_assert(u);
pa_assert(u->service_fd >= 0);
pa_assert(msg);
if (room <= 0)
room = BT_SUGGESTED_BUFFER_SIZE;
pa_assert(room >= sizeof(*msg));
pa_log_debug("Trying to receive message from audio service...");
@ -234,6 +234,11 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r
return -1;
}
if (msg->length > room) {
pa_log_error("Not enough room.");
return -1;
}
/* Secondly, read the payload */
if (msg->length > sizeof(*msg)) {
@ -307,7 +312,7 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa
pa_log_debug("Payload size is %lu %lu", (unsigned long) bytes_left, (unsigned long) sizeof(*codec));
if ((u->profile == PROFILE_A2DP && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) ||
if (((u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) ||
(u->profile == PROFILE_HSP && codec->transport != BT_CAPABILITIES_TRANSPORT_SCO)) {
pa_log_error("Got capabilities for wrong codec.");
return -1;
@ -340,6 +345,26 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa
pa_assert(codec->type == BT_A2DP_SBC_SINK);
if (codec->configured && seid == 0)
return codec->seid;
memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities));
} else if (u->profile == PROFILE_A2DP_SOURCE) {
while (bytes_left > 0) {
if ((codec->type == BT_A2DP_SBC_SOURCE) && !codec->lock)
break;
bytes_left -= codec->length;
codec = (const codec_capabilities_t*) ((const uint8_t*) codec + codec->length);
}
if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities))
return -1;
pa_assert(codec->type == BT_A2DP_SBC_SOURCE);
if (codec->configured && seid == 0)
return codec->seid;
@ -368,7 +393,7 @@ static int get_caps(struct userdata *u, uint8_t seid) {
msg.getcaps_req.seid = seid;
pa_strlcpy(msg.getcaps_req.object, u->path, sizeof(msg.getcaps_req.object));
if (u->profile == PROFILE_A2DP)
if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE)
msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
else {
pa_assert(u->profile == PROFILE_HSP);
@ -451,7 +476,7 @@ static int setup_a2dp(struct userdata *u) {
};
pa_assert(u);
pa_assert(u->profile == PROFILE_A2DP);
pa_assert(u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE);
cap = &u->a2dp.sbc_capabilities;
@ -652,8 +677,8 @@ static int set_conf(struct userdata *u) {
msg.open_req.h.length = sizeof(msg.open_req);
pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object));
msg.open_req.seid = u->profile == PROFILE_A2DP ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1;
msg.open_req.lock = u->profile == PROFILE_A2DP ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
msg.open_req.seid = (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1;
msg.open_req.lock = (u->profile == PROFILE_A2DP) ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
if (service_send(u, &msg.open_req.h) < 0)
return -1;
@ -661,7 +686,7 @@ static int set_conf(struct userdata *u) {
if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0)
return -1;
if (u->profile == PROFILE_A2DP ) {
if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
u->sample_spec.format = PA_SAMPLE_S16LE;
if (setup_a2dp(u) < 0)
@ -679,7 +704,7 @@ static int set_conf(struct userdata *u) {
msg.setconf_req.h.name = BT_SET_CONFIGURATION;
msg.setconf_req.h.length = sizeof(msg.setconf_req);
if (u->profile == PROFILE_A2DP) {
if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities));
} else {
msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
@ -697,7 +722,7 @@ static int set_conf(struct userdata *u) {
u->link_mtu = msg.setconf_rsp.link_mtu;
/* setup SBC encoder now we agree on parameters */
if (u->profile == PROFILE_A2DP) {
if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
setup_sbc(&u->a2dp);
u->block_size =
@ -881,7 +906,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
*((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
}
*((pa_usec_t*) data) += u->sink->fixed_latency;
*((pa_usec_t*) data) += u->sink->thread_info.fixed_latency;
return 0;
}
}
@ -943,7 +968,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
*((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->fixed_latency;
*((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->thread_info.fixed_latency;
return 0;
}
@ -1250,6 +1275,119 @@ static int a2dp_process_render(struct userdata *u) {
return ret;
}
static int a2dp_process_push(struct userdata *u) {
int ret = 0;
pa_memchunk memchunk;
pa_assert(u);
pa_assert(u->profile == PROFILE_A2DP_SOURCE);
pa_assert(u->source);
pa_assert(u->read_smoother);
memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size);
memchunk.index = memchunk.length = 0;
for (;;) {
pa_bool_t found_tstamp = FALSE;
pa_usec_t tstamp;
struct a2dp_info *a2dp;
struct rtp_header *header;
struct rtp_payload *payload;
const void *p;
void *d;
ssize_t l;
size_t to_write, to_decode;
unsigned frame_count;
a2dp_prepare_buffer(u);
a2dp = &u->a2dp;
header = a2dp->buffer;
payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type);
if (l <= 0) {
if (l < 0 && errno == EINTR)
/* Retry right away if we got interrupted */
continue;
else if (l < 0 && errno == EAGAIN)
/* Hmm, apparently the socket was not readable, give up for now. */
break;
pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
ret = -1;
break;
}
pa_assert((size_t) l <= a2dp->buffer_size);
u->read_index += (uint64_t) l;
/* TODO: get timestamp from rtp */
if (!found_tstamp) {
/* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */
tstamp = pa_rtclock_now();
}
pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
pa_smoother_resume(u->read_smoother, tstamp, TRUE);
p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
to_decode = l - sizeof(*header) - sizeof(*payload);
d = pa_memblock_acquire(memchunk.memblock);
to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
while (PA_LIKELY(to_decode > 0 && to_write > 0)) {
size_t written;
ssize_t decoded;
decoded = sbc_decode(&a2dp->sbc,
p, to_decode,
d, to_write,
&written);
if (PA_UNLIKELY(decoded <= 0)) {
pa_log_error("SBC decoding error (%li)", (long) decoded);
pa_memblock_release(memchunk.memblock);
pa_memblock_unref(memchunk.memblock);
return -1;
}
/* pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */
/* pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */
pa_assert_fp((size_t) decoded <= to_decode);
pa_assert_fp((size_t) decoded == a2dp->frame_length);
pa_assert_fp((size_t) written <= to_write);
pa_assert_fp((size_t) written == a2dp->codesize);
p = (const uint8_t*) p + decoded;
to_decode -= decoded;
d = (uint8_t*) d + written;
to_write -= written;
frame_count++;
}
pa_memblock_release(memchunk.memblock);
pa_source_post(u->source, &memchunk);
ret = 1;
break;
}
pa_memblock_unref(memchunk.memblock);
return ret;
}
static void thread_func(void *userdata) {
struct userdata *u = userdata;
unsigned do_write = 0;
@ -1262,11 +1400,11 @@ static void thread_func(void *userdata) {
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);
if (start_stream_fd(u) < 0)
goto fail;
pa_thread_mq_install(&u->thread_mq);
for (;;) {
struct pollfd *pollfd;
int ret;
@ -1285,7 +1423,12 @@ static void thread_func(void *userdata) {
if (pollfd && (pollfd->revents & POLLIN)) {
int n_read;
if ((n_read = hsp_process_push(u)) < 0)
if (u->profile == PROFILE_HSP)
n_read = hsp_process_push(u);
else
n_read = a2dp_process_push(u);
if (n_read < 0)
goto fail;
/* We just read something, so we are supposed to write something, too */
@ -1319,11 +1462,13 @@ static void thread_func(void *userdata) {
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);
if (skip_bytes > 0) {
pa_memchunk tmp;
pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
(unsigned long long) skip_usec,
(unsigned long long) skip_bytes);
@ -1332,6 +1477,7 @@ static void thread_func(void *userdata) {
pa_memblock_unref(tmp.memblock);
u->write_index += skip_bytes;
}
}
do_write = 1;
}
@ -1446,12 +1592,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) {
pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_sink_volume_changed(u->sink, &v, TRUE);
pa_sink_volume_changed(u->sink, &v);
} else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) {
pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_source_volume_changed(u->source, &v, TRUE);
pa_source_volume_changed(u->source, &v);
}
}
}
@ -1473,12 +1619,12 @@ static void sink_set_volume_cb(pa_sink *s) {
if (u->profile != PROFILE_HSP)
return;
gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
gain = (pa_cvolume_max(&s->real_volume) * 15) / PA_VOLUME_NORM;
if (gain > 15)
gain = 15;
pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_cvolume_set(&s->real_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetSpeakerGain"));
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
@ -1497,12 +1643,12 @@ static void source_set_volume_cb(pa_source *s) {
if (u->profile != PROFILE_HSP)
return;
gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
gain = (pa_cvolume_max(&s->volume) * 15) / PA_VOLUME_NORM;
if (gain > 15)
gain = 15;
pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_cvolume_set(&s->volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain"));
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
@ -1684,7 +1830,7 @@ static int add_source(struct userdata *u) {
data.driver = __FILE__;
data.module = u->module;
pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp");
pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP_SOURCE ? "a2dp_source" : "hsp");
if (u->profile == PROFILE_HSP)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
data.card = u->card;
@ -1709,7 +1855,7 @@ static int add_source(struct userdata *u) {
u->source->parent.process_msg = source_process_msg;
pa_source_set_fixed_latency(u->source,
(/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) +
(u->profile == PROFILE_A2DP_SOURCE ? FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
pa_bytes_to_usec(u->block_size, &u->sample_spec));
}
@ -1736,7 +1882,8 @@ static void shutdown_bt(struct userdata *u) {
if (u->service_fd >= 0) {
pa_close(u->service_fd);
u->service_fd = -1;
u->service_write_type = u->service_write_type = 0;
u->service_write_type = 0;
u->service_read_type = 0;
}
if (u->write_memchunk.memblock) {
@ -1752,7 +1899,8 @@ static int init_bt(struct userdata *u) {
shutdown_bt(u);
u->stream_write_type = 0;
u->service_write_type = u->service_write_type = 0;
u->service_write_type = 0;
u->service_read_type = 0;
if ((u->service_fd = bt_audio_service_open()) < 0) {
pa_log_error("Couldn't connect to bluetooth audio service");
@ -1804,7 +1952,8 @@ static int init_profile(struct userdata *u) {
if (add_sink(u) < 0)
r = -1;
if (u->profile == PROFILE_HSP)
if (u->profile == PROFILE_HSP ||
u->profile == PROFILE_A2DP_SOURCE)
if (add_source(u) < 0)
r = -1;
@ -2045,6 +2194,20 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
pa_hashmap_put(data.profiles, p->name, p);
}
if (pa_bluetooth_uuid_has(device->uuids, A2DP_SOURCE_UUID)) {
p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile));
p->priority = 10;
p->n_sinks = 0;
p->n_sources = 1;
p->max_sink_channels = 0;
p->max_source_channels = 2;
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_A2DP_SOURCE;
pa_hashmap_put(data.profiles, p->name, p);
}
if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) ||
pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) {
p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile));

View file

@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
Copyright 2008 Joao Paulo Rechi Vita
Copyright 2008-2009 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -84,7 +84,7 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const
mi = pa_hashmap_get(u->hashmap, d->path);
if (!d->dead &&
d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) {
d->device_connected > 0 && (d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED || d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED)) {
if (!mi) {
pa_module *m = NULL;
@ -116,6 +116,9 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const
}
#endif
if (d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED)
args = pa_sprintf_malloc("%s profile=\"a2dp_source\"", args);
pa_log_debug("Loading module-bluetooth-device %s", args);
m = pa_module_load(u->module->core, "module-bluetooth-device", args);
pa_xfree(args);

View file

@ -52,9 +52,6 @@ PA_MODULE_LOAD_ONCE(TRUE);
#define MAX_MODULES 10
#define BUF_MAX 2048
/* #undef PA_GCONF_HELPER */
/* #define PA_GCONF_HELPER "/home/lennart/projects/pulseaudio/src/gconf-helper" */
struct module_item {
char *name;
char *args;
@ -343,7 +340,11 @@ int pa__init(pa_module*m) {
u->io_event = NULL;
u->buf_fill = 0;
if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0)
if ((u->fd = pa_start_child_for_read(
#if defined(__linux__) && !defined(__OPTIMIZE__)
pa_run_from_build_tree() ? PA_BUILDDIR "/.libs/gconf-helper" :
#endif
PA_GCONF_HELPER, NULL, &u->pid)) < 0)
goto fail;
u->io_event = m->core->mainloop->io_new(

View file

@ -65,7 +65,7 @@ int pa_hal_get_info(pa_core *core, pa_proplist *p, int card) {
goto finish;
}
if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error)) < 0) {
if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error))) {
pa_log_error("Couldn't find devices: %s: %s", error.name, error.message);
goto finish;
}

View file

@ -24,6 +24,7 @@
#endif
#include <pulse/xmalloc.h>
#include <pulse/i18n.h>
#include <pulsecore/core.h>
#include <pulsecore/sink-input.h>
@ -35,7 +36,7 @@
#include "module-always-sink-symdef.h"
PA_MODULE_AUTHOR("Colin Guthrie");
PA_MODULE_DESCRIPTION("Always keeps at least one sink loaded even if it's a null one");
PA_MODULE_DESCRIPTION(_("Always keeps at least one sink loaded even if it's a null one"));
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
PA_MODULE_USAGE(
@ -78,7 +79,8 @@ static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata*
u->ignore = TRUE;
t = pa_sprintf_malloc("sink_name=%s", u->sink_name);
t = pa_sprintf_malloc("sink_name=%s sink_properties='device.description=\"%s\"'", u->sink_name,
_("Dummy Output"));
m = pa_module_load(c, "module-null-sink", t);
u->null_module = m ? m->index : PA_INVALID_INDEX;
pa_xfree(t);

View file

@ -58,7 +58,7 @@ PA_MODULE_USAGE(
"sink_name=<name for the sink> "
"sink_properties=<properties for the sink> "
"slaves=<slave sinks> "
"adjust_time=<seconds> "
"adjust_time=<how often to readjust rates in s> "
"resample_method=<method> "
"format=<sample format> "
"rate=<sample rate> "
@ -69,7 +69,7 @@ PA_MODULE_USAGE(
#define MEMBLOCKQ_MAXLENGTH (1024*1024*16)
#define DEFAULT_ADJUST_TIME 10
#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)
#define BLOCK_USEC (PA_USEC_PER_MSEC * 200)
@ -91,6 +91,7 @@ struct output {
pa_sink *sink;
pa_sink_input *sink_input;
pa_bool_t ignore_state_change;
pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */
*outq; /* Message queue from this sink input to the sink thread */
@ -99,9 +100,12 @@ struct output {
pa_memblockq *memblockq;
/* For communication of the stream latencies to the main thread */
pa_usec_t total_latency;
/* For coomunication of the stream parameters to the sink thread */
pa_atomic_t max_request;
pa_atomic_t requested_latency;
PA_LLIST_FIELDS(struct output);
};
@ -116,7 +120,7 @@ struct userdata {
pa_rtpoll *rtpoll;
pa_time_event *time_event;
uint32_t adjust_time;
pa_usec_t adjust_time;
pa_bool_t automatic;
pa_bool_t auto_desc;
@ -125,8 +129,6 @@ struct userdata {
pa_resample_method_t resample_method;
struct timeval adjust_timestamp;
pa_usec_t block_usec;
pa_idxset* outputs; /* managed in main context */
@ -146,13 +148,16 @@ enum {
SINK_MESSAGE_REMOVE_OUTPUT,
SINK_MESSAGE_NEED,
SINK_MESSAGE_UPDATE_LATENCY,
SINK_MESSAGE_UPDATE_MAX_REQUEST
SINK_MESSAGE_UPDATE_MAX_REQUEST,
SINK_MESSAGE_UPDATE_REQUESTED_LATENCY
};
enum {
SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
};
static void output_disable(struct output *o);
static void output_enable(struct output *o);
static void output_free(struct output *o);
static int output_create_sink_input(struct output *o);
@ -172,7 +177,7 @@ static void adjust_rates(struct userdata *u) {
if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)))
return;
for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
PA_IDXSET_FOREACH(o, u->outputs, idx) {
pa_usec_t sink_latency;
if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
@ -189,6 +194,11 @@ static void adjust_rates(struct userdata *u) {
avg_total_latency += o->total_latency;
n++;
pa_log_debug("[%s] total=%0.2fms sink=%0.2fms ", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC, (double) sink_latency / PA_USEC_PER_MSEC);
if (o->total_latency > 10*PA_USEC_PER_SEC)
pa_log_warn("[%s] Total latency of output is very high (%0.2fms), most likely the audio timing in one of your drivers is broken.", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC);
}
if (min_total_latency == (pa_usec_t) -1)
@ -203,22 +213,22 @@ static void adjust_rates(struct userdata *u) {
base_rate = u->sink->sample_spec.rate;
for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
PA_IDXSET_FOREACH(o, u->outputs, idx) {
uint32_t r = base_rate;
if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
continue;
if (o->total_latency < target_latency)
r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC);
r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r);
else if (o->total_latency > target_latency)
r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC);
r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r);
if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) {
pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), base_rate, r);
pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->sink->name, base_rate, r);
pa_sink_input_set_rate(o->sink_input, base_rate);
} else {
pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), r, (double) r / base_rate, (float) o->total_latency);
pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->sink->name, r, (double) r / base_rate, (float) o->total_latency);
pa_sink_input_set_rate(o->sink_input, r);
}
}
@ -235,7 +245,7 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
adjust_rates(u);
pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time * PA_USEC_PER_SEC);
pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time);
}
static void process_render_null(struct userdata *u, pa_usec_t now) {
@ -355,18 +365,15 @@ static void render_memblock(struct userdata *u, struct output *o, size_t length)
u->thread_info.counter += chunk.length;
/* OK, let's send this data to the other threads */
for (j = u->thread_info.active_outputs; j; j = j->next)
PA_LLIST_FOREACH(j, u->thread_info.active_outputs) {
if (j == o)
continue;
/* Send to other outputs, which are not the requesting
* one */
if (j != o)
pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL);
}
/* And place it directly into the requesting output's queue */
if (o)
pa_memblockq_push_align(o->memblockq, &chunk);
pa_memblock_unref(chunk.memblock);
}
}
@ -402,10 +409,18 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
/* If necessary, get some new data */
request_memblock(o, nbytes);
/* pa_log("%s q size is %u + %u (%u/%u)", */
/* i->sink->name, */
/* pa_memblockq_get_nblocks(o->memblockq), */
/* pa_memblockq_get_nblocks(i->thread_info.render_memblockq), */
/* pa_memblockq_get_maxrewind(o->memblockq), */
/* pa_memblockq_get_maxrewind(i->thread_info.render_memblockq)); */
if (pa_memblockq_peek(o->memblockq, chunk) < 0)
return -1;
pa_memblockq_drop(o->memblockq, chunk->length);
return 0;
}
@ -440,13 +455,35 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
return;
pa_atomic_store(&o->max_request, (int) nbytes);
pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL);
}
/* Called from thread context */
static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
struct output *o;
pa_usec_t c;
pa_assert(i);
pa_sink_input_assert_ref(i);
pa_assert_se(o = i->userdata);
c = pa_sink_get_requested_latency_within_thread(i->sink);
if (c == (pa_usec_t) -1)
c = i->sink->thread_info.max_latency;
if (pa_atomic_load(&o->requested_latency) == (int) c)
return;
pa_atomic_store(&o->requested_latency, (int) c);
pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL);
}
/* Called from I/O thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct output *o;
pa_usec_t c;
pa_sink_input_assert_ref(i);
pa_assert_se(o = i->userdata);
@ -455,14 +492,24 @@ static void sink_input_attach_cb(pa_sink_input *i) {
pa_assert(!o->inq_rtpoll_item_read && !o->outq_rtpoll_item_write);
o->inq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
i->sink->rtpoll,
i->sink->thread_info.rtpoll,
PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */
o->inq);
o->outq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
i->sink->rtpoll,
i->sink->thread_info.rtpoll,
PA_RTPOLL_EARLY,
o->outq);
pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
pa_atomic_store(&o->max_request, (int) pa_sink_input_get_max_request(i));
c = pa_sink_get_requested_latency_within_thread(i->sink);
pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c));
pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL);
pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL);
}
/* Called from I/O thread context */
@ -472,14 +519,15 @@ static void sink_input_detach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(o = i->userdata);
/* Shut down the queue from the sink thread to us */
pa_assert(o->inq_rtpoll_item_read && o->outq_rtpoll_item_write);
if (o->inq_rtpoll_item_read) {
pa_rtpoll_item_free(o->inq_rtpoll_item_read);
o->inq_rtpoll_item_read = NULL;
}
if (o->outq_rtpoll_item_write) {
pa_rtpoll_item_free(o->outq_rtpoll_item_write);
o->outq_rtpoll_item_write = NULL;
}
}
/* Called from main context */
@ -493,20 +541,6 @@ static void sink_input_kill_cb(pa_sink_input *i) {
output_free(o);
}
/* Called from IO thread context */
static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
/* If we are added for the first time, ask for a rewinding so that
* we are heard right-away. */
if (PA_SINK_INPUT_IS_LINKED(state) &&
i->thread_info.state == PA_SINK_INPUT_INIT)
pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
}
/* Called from thread context */
static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct output *o = PA_SINK_INPUT(obj)->userdata;
@ -536,37 +570,6 @@ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64
return pa_sink_input_process_msg(obj, code, data, offset, chunk);
}
/* Called from main context */
static void disable_output(struct output *o) {
pa_assert(o);
if (!o->sink_input)
return;
pa_sink_input_unlink(o->sink_input);
pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
pa_sink_input_unref(o->sink_input);
o->sink_input = NULL;
}
/* Called from main context */
static void enable_output(struct output *o) {
pa_assert(o);
if (o->sink_input)
return;
if (output_create_sink_input(o) >= 0) {
pa_memblockq_flush_write(o->memblockq);
pa_sink_input_put(o->sink_input);
if (o->userdata->sink && PA_SINK_IS_LINKED(pa_sink_get_state(o->userdata->sink)))
pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
}
}
/* Called from main context */
static void suspend(struct userdata *u) {
struct output *o;
@ -575,8 +578,8 @@ static void suspend(struct userdata *u) {
pa_assert(u);
/* Let's suspend by unlinking all streams */
for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
disable_output(o);
PA_IDXSET_FOREACH(o, u->outputs, idx)
output_disable(o);
pa_log_info("Device suspended...");
}
@ -589,13 +592,8 @@ static void unsuspend(struct userdata *u) {
pa_assert(u);
/* Let's resume */
for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
pa_sink_suspend(o->sink, FALSE, PA_SUSPEND_IDLE);
if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
enable_output(o);
}
PA_IDXSET_FOREACH(o, u->outputs, idx)
output_enable(o);
pa_log_info("Resumed successfully...");
}
@ -639,7 +637,13 @@ static void update_max_request(struct userdata *u) {
size_t max_request = 0;
struct output *o;
for (o = u->thread_info.active_outputs; o; o = o->next) {
pa_assert(u);
pa_sink_assert_io_context(u->sink);
/* Collects the max_request values of all streams and sets the
* largest one locally */
PA_LLIST_FOREACH(o, u->thread_info.active_outputs) {
size_t mr = (size_t) pa_atomic_load(&o->max_request);
if (mr > max_request)
@ -652,6 +656,67 @@ static void update_max_request(struct userdata *u) {
pa_sink_set_max_request_within_thread(u->sink, max_request);
}
/* Called from IO context */
static void update_fixed_latency(struct userdata *u) {
pa_usec_t fixed_latency = 0;
struct output *o;
pa_assert(u);
pa_sink_assert_io_context(u->sink);
/* Collects the requested_latency values of all streams and sets
* the largest one as fixed_latency locally */
PA_LLIST_FOREACH(o, u->thread_info.active_outputs) {
pa_usec_t rl = (size_t) pa_atomic_load(&o->requested_latency);
if (rl > fixed_latency)
fixed_latency = rl;
}
if (fixed_latency <= 0)
fixed_latency = u->block_usec;
pa_sink_set_fixed_latency_within_thread(u->sink, fixed_latency);
}
/* Called from thread context of the io thread */
static void output_add_within_thread(struct output *o) {
pa_assert(o);
pa_sink_assert_io_context(o->sink);
PA_LLIST_PREPEND(struct output, o->userdata->thread_info.active_outputs, o);
pa_assert(!o->outq_rtpoll_item_read && !o->inq_rtpoll_item_write);
o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
o->userdata->rtpoll,
PA_RTPOLL_EARLY-1, /* This item is very important */
o->outq);
o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
o->userdata->rtpoll,
PA_RTPOLL_EARLY,
o->inq);
}
/* Called from thread context of the io thread */
static void output_remove_within_thread(struct output *o) {
pa_assert(o);
pa_sink_assert_io_context(o->sink);
PA_LLIST_REMOVE(struct output, o->userdata->thread_info.active_outputs, o);
if (o->outq_rtpoll_item_read) {
pa_rtpoll_item_free(o->outq_rtpoll_item_read);
o->outq_rtpoll_item_read = NULL;
}
if (o->inq_rtpoll_item_write) {
pa_rtpoll_item_free(o->inq_rtpoll_item_write);
o->inq_rtpoll_item_write = NULL;
}
}
/* Called from thread context of the io thread */
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
@ -684,42 +749,17 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
return 0;
}
case SINK_MESSAGE_ADD_OUTPUT: {
struct output *op = data;
PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op);
pa_assert(!op->outq_rtpoll_item_read && !op->inq_rtpoll_item_write);
op->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
u->rtpoll,
PA_RTPOLL_EARLY-1, /* This item is very important */
op->outq);
op->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
u->rtpoll,
PA_RTPOLL_EARLY,
op->inq);
case SINK_MESSAGE_ADD_OUTPUT:
output_add_within_thread(data);
update_max_request(u);
update_fixed_latency(u);
return 0;
}
case SINK_MESSAGE_REMOVE_OUTPUT: {
struct output *op = data;
PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op);
pa_assert(op->outq_rtpoll_item_read && op->inq_rtpoll_item_write);
pa_rtpoll_item_free(op->outq_rtpoll_item_read);
op->outq_rtpoll_item_read = NULL;
pa_rtpoll_item_free(op->inq_rtpoll_item_write);
op->inq_rtpoll_item_write = NULL;
case SINK_MESSAGE_REMOVE_OUTPUT:
output_remove_within_thread(data);
update_max_request(u);
update_fixed_latency(u);
return 0;
}
case SINK_MESSAGE_NEED:
render_memblock(u, (struct output*) data, (size_t) offset);
@ -741,10 +781,13 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
}
case SINK_MESSAGE_UPDATE_MAX_REQUEST:
update_max_request(u);
break;
}
case SINK_MESSAGE_UPDATE_REQUESTED_LATENCY:
update_fixed_latency(u);
break;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
@ -767,7 +810,7 @@ static void update_description(struct userdata *u) {
t = pa_xstrdup("Simultaneous output to");
for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
PA_IDXSET_FOREACH(o, u->outputs, idx) {
char *e;
if (first) {
@ -801,8 +844,9 @@ static int output_create_sink_input(struct output *o) {
pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map);
data.module = o->userdata->module;
data.resample_method = o->userdata->resample_method;
data.flags = PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE|PA_SINK_INPUT_NO_CREATE_ON_SUSPEND;
pa_sink_input_new(&o->sink_input, o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE);
pa_sink_input_new(&o->sink_input, o->userdata->core, &data);
pa_sink_input_new_data_done(&data);
@ -812,9 +856,9 @@ static int output_create_sink_input(struct output *o) {
o->sink_input->parent.process_msg = sink_input_process_msg;
o->sink_input->pop = sink_input_pop_cb;
o->sink_input->process_rewind = sink_input_process_rewind_cb;
o->sink_input->state_change = sink_input_state_change_cb;
o->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
o->sink_input->update_max_request = sink_input_update_max_request_cb;
o->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb;
o->sink_input->attach = sink_input_attach_cb;
o->sink_input->detach = sink_input_detach_cb;
o->sink_input->kill = sink_input_kill_cb;
@ -825,22 +869,19 @@ static int output_create_sink_input(struct output *o) {
return 0;
}
/* Called from main context */
static struct output *output_new(struct userdata *u, pa_sink *sink) {
struct output *o;
pa_sink_state_t state;
pa_assert(u);
pa_assert(sink);
pa_assert(u->sink);
o = pa_xnew(struct output, 1);
o = pa_xnew0(struct output, 1);
o->userdata = u;
o->inq = pa_asyncmsgq_new(0);
o->outq = pa_asyncmsgq_new(0);
o->inq_rtpoll_item_write = o->inq_rtpoll_item_read = NULL;
o->outq_rtpoll_item_write = o->outq_rtpoll_item_read = NULL;
o->sink = sink;
o->sink_input = NULL;
o->memblockq = pa_memblockq_new(
0,
MEMBLOCKQ_MAXLENGTH,
@ -850,53 +891,31 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) {
0,
0,
NULL);
pa_atomic_store(&o->max_request, 0);
PA_LLIST_INIT(struct output, o);
pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0);
state = pa_sink_get_state(u->sink);
if (state != PA_SINK_INIT)
pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
else {
/* If the sink is not yet started, we need to do the activation ourselves */
PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o);
o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
u->rtpoll,
PA_RTPOLL_EARLY-1, /* This item is very important */
o->outq);
o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
u->rtpoll,
PA_RTPOLL_EARLY,
o->inq);
}
if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) {
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)
goto fail;
}
update_description(u);
return o;
}
fail:
/* Called from main context */
static void output_free(struct output *o) {
pa_assert(o);
if (o) {
pa_idxset_remove_by_data(u->outputs, o, NULL);
output_disable(o);
if (o->sink_input) {
pa_sink_input_unlink(o->sink_input);
pa_sink_input_unref(o->sink_input);
}
pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL));
update_description(o->userdata);
if (o->memblockq)
pa_memblockq_free(o->memblockq);
if (o->inq_rtpoll_item_read)
pa_rtpoll_item_free(o->inq_rtpoll_item_read);
if (o->inq_rtpoll_item_write)
pa_rtpoll_item_free(o->inq_rtpoll_item_write);
if (o->outq_rtpoll_item_read)
pa_rtpoll_item_free(o->outq_rtpoll_item_read);
if (o->outq_rtpoll_item_write)
pa_rtpoll_item_free(o->outq_rtpoll_item_write);
if (o->inq)
pa_asyncmsgq_unref(o->inq);
@ -904,30 +923,103 @@ fail:
if (o->outq)
pa_asyncmsgq_unref(o->outq);
pa_xfree(o);
}
if (o->memblockq)
pa_memblockq_free(o->memblockq);
return NULL;
pa_xfree(o);
}
/* Called from main context */
static void output_enable(struct output *o) {
pa_assert(o);
if (o->sink_input)
return;
/* This might cause the sink to be resumed. The state change hook
* of the sink might hence be called from here, which might then
* cause us to be called in a loop. Make sure that state changes
* for this output don't cause this loop by setting a flag here */
o->ignore_state_change = TRUE;
if (output_create_sink_input(o) >= 0) {
if (pa_sink_get_state(o->sink) != PA_SINK_INIT) {
/* First we register the output. That means that the sink
* will start to pass data to this output. */
pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
/* Then we enable the sink input. That means that the sink
* is now asked for new data. */
pa_sink_input_put(o->sink_input);
} else
/* Hmm the sink is not yet started, do things right here */
output_add_within_thread(o);
}
o->ignore_state_change = FALSE;
}
/* Called from main context */
static void output_disable(struct output *o) {
pa_assert(o);
if (!o->sink_input)
return;
/* First we disable the sink input. That means that the sink is
* not asked for new data anymore */
pa_sink_input_unlink(o->sink_input);
/* Then we unregister the output. That means that the sink doesn't
* pass any further data to this output */
pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
/* Now dellocate the stream */
pa_sink_input_unref(o->sink_input);
o->sink_input = NULL;
/* Finally, drop all queued data */
pa_memblockq_flush_write(o->memblockq);
pa_asyncmsgq_flush(o->inq, FALSE);
pa_asyncmsgq_flush(o->outq, FALSE);
}
/* Called from main context */
static void output_verify(struct output *o) {
pa_assert(o);
if (PA_SINK_IS_OPENED(pa_sink_get_state(o->userdata->sink)))
output_enable(o);
else
output_disable(o);
}
/* Called from main context */
static pa_bool_t is_suitable_sink(struct userdata *u, pa_sink *s) {
const char *t;
pa_sink_assert_ref(s);
if (!(s->flags & PA_SINK_HARDWARE))
return FALSE;
if (s == u->sink)
return FALSE;
if (!(s->flags & PA_SINK_HARDWARE))
return FALSE;
if (!(s->flags & PA_SINK_LATENCY))
return FALSE;
if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS)))
if (strcmp(t, "sound"))
if (!pa_streq(t, "sound"))
return FALSE;
return TRUE;
}
/* Called from main context */
static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
struct output *o;
@ -940,18 +1032,17 @@ static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata
return PA_HOOK_OK;
pa_log_info("Configuring new sink: %s", s->name);
if (!(o = output_new(u, s))) {
pa_log("Failed to create sink input on sink '%s'.", s->name);
return PA_HOOK_OK;
}
if (o->sink_input)
pa_sink_input_put(o->sink_input);
output_verify(o);
return PA_HOOK_OK;
}
/* Called from main context */
static struct output* find_output(struct userdata *u, pa_sink *s) {
struct output *o;
uint32_t idx;
@ -962,13 +1053,14 @@ static struct output* find_output(struct userdata *u, pa_sink *s) {
if (u->sink == s)
return NULL;
for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
PA_IDXSET_FOREACH(o, u->outputs, idx)
if (o->sink == s)
return o;
return NULL;
}
/* Called from main context */
static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
struct output *o;
@ -980,26 +1072,25 @@ static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userd
return PA_HOOK_OK;
pa_log_info("Unconfiguring sink: %s", s->name);
output_free(o);
return PA_HOOK_OK;
}
/* Called from main context */
static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
struct output *o;
pa_sink_state_t state;
if (!(o = find_output(u, s)))
return PA_HOOK_OK;
state = pa_sink_get_state(s);
/* This state change might be triggered because we are creating a
* stream here, in that case we don't want to create it a second
* time here and enter a loop */
if (o->ignore_state_change)
return PA_HOOK_OK;
if (PA_SINK_IS_OPENED(state) && PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input)
enable_output(o);
if (state == PA_SINK_SUSPENDED && o->sink_input)
disable_output(o);
output_verify(o);
return PA_HOOK_OK;
}
@ -1014,6 +1105,7 @@ int pa__init(pa_module*m) {
struct output *o;
uint32_t idx;
pa_sink_new_data data;
uint32_t adjust_time_sec;
pa_assert(m);
@ -1029,23 +1121,13 @@ int pa__init(pa_module*m) {
}
}
m->userdata = u = pa_xnew(struct userdata, 1);
m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
u->sink = NULL;
u->time_event = NULL;
u->adjust_time = DEFAULT_ADJUST_TIME;
u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
u->thread = NULL;
u->resample_method = resample_method;
u->outputs = pa_idxset_new(NULL, NULL);
memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp));
u->sink_put_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL;
PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs);
pa_atomic_store(&u->thread_info.running, FALSE);
u->thread_info.in_null_mode = FALSE;
u->thread_info.counter = 0;
u->thread_info.smoother = pa_smoother_new(
PA_USEC_PER_SEC,
PA_USEC_PER_SEC*2,
@ -1055,16 +1137,73 @@ int pa__init(pa_module*m) {
0,
FALSE);
if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {
adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
pa_log("Failed to parse adjust_time value");
goto fail;
}
if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
else
u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
slaves = pa_modargs_get_value(ma, "slaves", NULL);
u->automatic = !slaves;
ss = m->core->default_sample_spec;
map = m->core->default_channel_map;
/* Check the specified slave sinks for sample_spec and channel_map to use for the combined sink */
if (!u->automatic) {
const char*split_state = NULL;
char *n = NULL;
pa_sample_spec slaves_spec;
pa_channel_map slaves_map;
pa_bool_t is_first_slave = TRUE;
pa_sample_spec_init(&slaves_spec);
while ((n = pa_split(slaves, ",", &split_state))) {
pa_sink *slave_sink;
if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK))) {
pa_log("Invalid slave sink '%s'", n);
pa_xfree(n);
goto fail;
}
pa_xfree(n);
if (is_first_slave) {
slaves_spec = slave_sink->sample_spec;
slaves_map = slave_sink->channel_map;
is_first_slave = FALSE;
} else {
if (slaves_spec.format != slave_sink->sample_spec.format)
slaves_spec.format = PA_SAMPLE_INVALID;
if (slaves_spec.rate < slave_sink->sample_spec.rate)
slaves_spec.rate = slave_sink->sample_spec.rate;
if (!pa_channel_map_equal(&slaves_map, &slave_sink->channel_map))
slaves_spec.channels = 0;
}
}
if (!is_first_slave) {
if (slaves_spec.format != PA_SAMPLE_INVALID)
ss.format = slaves_spec.format;
ss.rate = slaves_spec.rate;
if (slaves_spec.channels > 0) {
map = slaves_map;
ss.channels = slaves_map.channels;
}
}
}
if ((pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0)) {
pa_log("Invalid sample specification.");
goto fail;
@ -1095,7 +1234,6 @@ int pa__init(pa_module*m) {
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Simultaneous Output");
}
u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY);
pa_sink_new_data_done(&data);
@ -1149,7 +1287,7 @@ int pa__init(pa_module*m) {
/* We're in automatic mode, we add every sink that matches our needs */
for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) {
PA_IDXSET_FOREACH(s, m->core->sinks, idx) {
if (!is_suitable_sink(u, s))
continue;
@ -1174,12 +1312,11 @@ int pa__init(pa_module*m) {
/* Activate the sink and the sink inputs */
pa_sink_put(u->sink);
for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
if (o->sink_input)
pa_sink_input_put(o->sink_input);
PA_IDXSET_FOREACH(o, u->outputs, idx)
output_verify(o);
if (u->adjust_time > 0)
u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time * PA_USEC_PER_SEC, time_callback, u);
u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
pa_modargs_free(ma);
@ -1195,37 +1332,6 @@ fail:
return -1;
}
static void output_free(struct output *o) {
pa_assert(o);
disable_output(o);
pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL));
update_description(o->userdata);
if (o->inq_rtpoll_item_read)
pa_rtpoll_item_free(o->inq_rtpoll_item_read);
if (o->inq_rtpoll_item_write)
pa_rtpoll_item_free(o->inq_rtpoll_item_write);
if (o->outq_rtpoll_item_read)
pa_rtpoll_item_free(o->outq_rtpoll_item_read);
if (o->outq_rtpoll_item_write)
pa_rtpoll_item_free(o->outq_rtpoll_item_write);
if (o->inq)
pa_asyncmsgq_unref(o->inq);
if (o->outq)
pa_asyncmsgq_unref(o->outq);
if (o->memblockq)
pa_memblockq_free(o->memblockq);
pa_xfree(o);
}
void pa__done(pa_module*m) {
struct userdata *u;
struct output *o;

View file

@ -187,7 +187,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
}
add_session(u, path);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) {
@ -202,7 +201,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
}
remove_session(u, path);
return DBUS_HANDLER_RESULT_HANDLED;
}
finish:

View file

@ -50,7 +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!");
PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-detect!");
static const char* const valid_modargs[] = {
"just-one",
@ -119,7 +119,7 @@ static int detect_alsa(pa_core *c, int just_one) {
}
#endif
#ifdef HAVE_OSS
#ifdef HAVE_OSS_OUTPUT
static int detect_oss(pa_core *c, int just_one) {
FILE *f;
int n = 0, b = 0;
@ -240,7 +240,7 @@ int pa__init(pa_module*m) {
#ifdef HAVE_ALSA
if ((n = detect_alsa(m->core, just_one)) <= 0)
#endif
#ifdef HAVE_OSS
#ifdef HAVE_OSS_OUTPUT
if ((n = detect_oss(m->core, just_one)) <= 0)
#endif
#ifdef HAVE_SOLARIS

View file

@ -218,7 +218,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
if (sink->save_volume) {
entry.channel_map = sink->channel_map;
entry.volume = *pa_sink_get_volume(sink, FALSE, TRUE);
entry.volume = *pa_sink_get_volume(sink, FALSE);
entry.volume_valid = TRUE;
}

View file

@ -0,0 +1,84 @@
/***
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/module.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/core-util.h>
#include "module-hal-detect-symdef.h"
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-udev-detect instead of module-hal-detect!");
static const char* const valid_modargs[] = {
"api",
"tsched",
"subdevices",
NULL,
};
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
pa_bool_t tsched = TRUE;
pa_module *n;
char *t;
pa_assert(m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments");
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "tsched", &tsched) < 0) {
pa_log("tsched= expects boolean arguments");
goto fail;
}
pa_log_warn("We will now load module-udev-detect. Please make sure to remove module-hal-detect from your configuration.");
t = pa_sprintf_malloc("tsched=%s", pa_yes_no(tsched));
n = pa_module_load(m->core, "module-udev-detect", t);
pa_xfree(t);
if (n)
pa_module_unload_request(m, TRUE);
pa_modargs_free(ma);
return n ? 0 : -1;
fail:
if (ma)
pa_modargs_free(ma);
return -1;
}

View file

@ -55,14 +55,16 @@ PA_MODULE_AUTHOR("Shahms King");
PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
#if defined(HAVE_ALSA) && defined(HAVE_OSS)
#if defined(HAVE_ALSA) && defined(HAVE_OSS_OUTPUT)
PA_MODULE_USAGE("api=<alsa or oss> "
"tsched=<enable system timer based scheduling mode?>");
"tsched=<enable system timer based scheduling mode?>"
"subdevices=<init all subdevices>");
#elif defined(HAVE_ALSA)
PA_MODULE_USAGE("api=<alsa> "
"tsched=<enable system timer based scheduling mode?>");
#elif defined(HAVE_OSS)
PA_MODULE_USAGE("api=<oss>");
#elif defined(HAVE_OSS_OUTPUT)
PA_MODULE_USAGE("api=<oss>"
"subdevices=<init all subdevices>");
#endif
PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");
@ -82,6 +84,9 @@ struct userdata {
#ifdef HAVE_ALSA
pa_bool_t use_tsched;
#endif
#ifdef HAVE_OSS_OUTPUT
pa_bool_t init_subdevs;
#endif
};
#define CAPABILITY_ALSA "alsa"
@ -91,6 +96,9 @@ static const char* const valid_modargs[] = {
"api",
#ifdef HAVE_ALSA
"tsched",
#endif
#ifdef HAVE_OSS_OUTPUT
"subdevices",
#endif
NULL
};
@ -262,9 +270,9 @@ fail:
#endif
#ifdef HAVE_OSS
#ifdef HAVE_OSS_OUTPUT
static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi) {
static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi, pa_bool_t init_subdevices) {
char *class = NULL, *dev = NULL, *e;
int device;
pa_bool_t r = FALSE;
@ -294,7 +302,7 @@ static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi)
/* We only care for the main device */
device = libhal_device_get_property_int(context, udi, "oss.device", &error);
if (dbus_error_is_set(&error) || device != 0)
if (dbus_error_is_set(&error) || (device != 0 && init_subdevices == FALSE))
goto finish;
r = TRUE;
@ -324,7 +332,7 @@ static int hal_device_load_oss(struct userdata *u, const char *udi, struct devic
pa_assert(d);
/* We only care for OSS PCM devices */
if (!hal_oss_device_is_pcm(u->context, udi))
if (!hal_oss_device_is_pcm(u->context, udi, u->init_subdevs))
goto fail;
/* We store only one entry per card, hence we look for the originating device */
@ -394,7 +402,7 @@ static struct device* hal_device_add(struct userdata *u, const char *udi) {
if (pa_streq(u->capability, CAPABILITY_ALSA))
r = hal_device_load_alsa(u, udi, d);
#endif
#ifdef HAVE_OSS
#ifdef HAVE_OSS_OUTPUT
if (pa_streq(u->capability, CAPABILITY_OSS))
r = hal_device_load_oss(u, udi, d);
#endif
@ -427,9 +435,7 @@ static int hal_device_add_all(struct userdata *u) {
int i;
for (i = 0; i < n; i++) {
struct device *d;
if ((d = hal_device_add(u, udis[i]))) {
if (hal_device_add(u, udis[i])) {
count++;
pa_log_debug("Loaded device %s", udis[i]);
} else
@ -615,8 +621,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
}
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
/* We use this message to avoid a dirty race condition when we
get an ACLAdded message before the previously owning PA
@ -660,7 +664,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
/* Yes, we don't check the UDI for validity, but hopefully HAL will */
device_added_cb(u->context, udi);
return DBUS_HANDLER_RESULT_HANDLED;
}
finish:
@ -753,7 +756,7 @@ int pa__init(pa_module*m) {
api = pa_modargs_get_value(ma, "api", "oss");
#endif
#ifdef HAVE_OSS
#ifdef HAVE_OSS_OUTPUT
if (pa_streq(api, "oss"))
u->capability = CAPABILITY_OSS;
#endif
@ -763,6 +766,13 @@ int pa__init(pa_module*m) {
goto fail;
}
#ifdef HAVE_OSS_OUTPUT
if (pa_modargs_get_value_boolean(ma, "subdevices", &u->init_subdevs) < 0) {
pa_log("Failed to parse subdevices= argument.");
goto fail;
}
#endif
if (!(u->connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
goto fail;

View file

@ -127,6 +127,9 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
if (s == def)
continue;
if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)))
continue;
if (role_match(s->proplist, role)) {
new_data->sink = s;
new_data->save_sink = FALSE;
@ -173,6 +176,9 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
if (s == def)
continue;
if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)))
continue;
if (role_match(s->proplist, role)) {
new_data->source = s;
new_data->save_source = FALSE;
@ -201,6 +207,17 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct
if (si->save_sink)
continue;
/* Skip this if it is already in the process of being moved
* anyway */
if (!si->sink)
continue;
/* It might happen that a stream and a sink are set up at the
same time, in which case we want to make sure we don't
interfere with that */
if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
continue;
if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE)))
continue;
@ -237,6 +254,17 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
if (so->direct_on_input)
continue;
/* Skip this if it is already in the process of being moved
* anyway */
if (!so->source)
continue;
/* It might happen that a stream and a source are set up at the
same time, in which case we want to make sure we don't
interfere with that */
if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
continue;
if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE)))
continue;
@ -275,26 +303,30 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
uint32_t jdx;
pa_sink *d;
if (!si->sink)
continue;
if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE)))
continue;
/* Would the default sink fit? If so, let's use it */
if (def != sink && role_match(def->proplist, role)) {
pa_sink_input_move_to(si, def, FALSE);
if (def != sink && role_match(def->proplist, role))
if (pa_sink_input_move_to(si, def, FALSE) >= 0)
continue;
}
/* Try to find some other fitting sink */
PA_IDXSET_FOREACH(d, c->sinks, jdx) {
if (d == def || d == sink)
continue;
if (role_match(d->proplist, role)) {
pa_sink_input_move_to(si, d, FALSE);
if (!PA_SINK_IS_LINKED(pa_sink_get_state(d)))
continue;
if (role_match(d->proplist, role))
if (pa_sink_input_move_to(si, d, FALSE) >= 0)
break;
}
}
}
return PA_HOOK_OK;
}
@ -325,6 +357,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
if (so->direct_on_input)
continue;
if (!so->source)
continue;
if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE)))
continue;
@ -339,6 +374,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
if (d == def || d == source)
continue;
if (!PA_SOURCE_IS_LINKED(pa_source_get_state(d)))
continue;
if (role_match(d->proplist, role) && !source->monitor_of == !d->monitor_of) {
pa_source_output_move_to(so, d, FALSE);
break;

View file

@ -64,10 +64,9 @@ PA_MODULE_USAGE(
#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
struct userdata {
pa_core *core;
pa_module *module;
pa_sink *sink, *master;
pa_sink *sink;
pa_sink_input *sink_input;
const LADSPA_Descriptor *descriptor;
@ -83,6 +82,8 @@ struct userdata {
LADSPA_Data control_out;
pa_memblockq *memblockq;
pa_bool_t auto_desc;
};
static const char* const valid_modargs[] = {
@ -100,69 +101,111 @@ static const char* const valid_modargs[] = {
};
/* Called from I/O thread context */
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY: {
pa_usec_t usec = 0;
case PA_SINK_MESSAGE_GET_LATENCY:
/* Get the latency of the master sink */
if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
usec = 0;
/* Add the latency internal to our sink input on top */
usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec);
*((pa_usec_t*) data) = usec;
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it in that time. Also, the
* sink input is first shut down, the sink second. */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((pa_usec_t*) data) = 0;
return 0;
}
*((pa_usec_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
return 0;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
/* Called from main context */
static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (PA_SINK_IS_LINKED(state) &&
u->sink_input &&
PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
if (!PA_SINK_IS_LINKED(state) ||
!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
return 0;
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
/* Called from I/O thread context */
static void sink_request_rewind(pa_sink *s) {
static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE);
}
/* Called from I/O thread context */
static void sink_update_requested_latency(pa_sink *s) {
static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
pa_sink_get_requested_latency_within_thread(s));
}
/* Called from main context */
static void sink_set_volume_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
return;
pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
}
/* Called from main context */
static void sink_set_mute_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
return;
pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
}
/* Called from I/O thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
struct userdata *u;
@ -175,8 +218,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
pa_assert(chunk);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
return -1;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
pa_memchunk nchunk;
@ -225,9 +268,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
return;
if (u->sink->thread_info.rewind_nbytes > 0) {
size_t max_rewrite;
@ -263,9 +303,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_memblockq_set_maxrewind(u->memblockq, nbytes);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
@ -277,9 +314,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
@ -290,12 +324,19 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from I/O thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
@ -303,11 +344,8 @@ static void sink_input_detach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_detach_within_thread(u->sink);
pa_sink_set_asyncmsgq(u->sink, NULL);
pa_sink_set_rtpoll(u->sink, NULL);
}
@ -318,14 +356,13 @@ static void sink_input_attach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
pa_sink_attach_within_thread(u->sink);
pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency);
}
/* Called from main context */
@ -335,13 +372,17 @@ static void sink_input_kill_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_unlink(u->sink);
/* The order here matters! We first kill the sink input, followed
* by the sink. That means the sink callbacks must be protected
* against an unconnected sink input! */
pa_sink_input_unlink(u->sink_input);
pa_sink_unlink(u->sink);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_sink_unref(u->sink);
u->sink = NULL;
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_module_unload_request(u->module, TRUE);
}
@ -372,13 +413,59 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
return u->sink != dest;
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
if (u->auto_desc && dest) {
const char *z;
pa_proplist *pl;
pl = pa_proplist_new();
z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s",
pa_proplist_gets(u->sink->proplist, "device.ladspa.name"), z ? z : dest->name);
pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
pa_proplist_free(pl);
}
}
/* Called from main context */
static void sink_input_volume_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_volume_changed(u->sink, &i->volume);
}
/* Called from main context */
static void sink_input_mute_changed_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_mute_changed(u->sink, i->muted);
}
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss;
pa_channel_map map;
pa_modargs *ma;
char *t;
const char *z;
pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
@ -392,7 +479,7 @@ int pa__init(pa_module*m) {
pa_assert(m);
pa_assert(sizeof(LADSPA_Data) == sizeof(float));
pa_assert_cc(sizeof(LADSPA_Data) == sizeof(float));
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments.");
@ -425,12 +512,8 @@ int pa__init(pa_module*m) {
cdata = pa_modargs_get_value(ma, "control", NULL);
u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
m->userdata = u;
u->master = master;
u->sink = NULL;
u->sink_input = NULL;
u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL);
if (!(e = getenv("LADSPA_PATH")))
@ -694,11 +777,8 @@ int pa__init(pa_module*m) {
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name);
sink_data.namereg_fail = FALSE;
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &map);
z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin);
@ -714,7 +794,16 @@ int pa__init(pa_module*m) {
goto fail;
}
u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY);
if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *z;
z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", d->Name, z ? z : master->name);
}
u->sink = pa_sink_new(m->core, &sink_data,
PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
(master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
@ -722,26 +811,27 @@ int pa__init(pa_module*m) {
goto fail;
}
u->sink->parent.process_msg = sink_process_msg;
u->sink->set_state = sink_set_state;
u->sink->update_requested_latency = sink_update_requested_latency;
u->sink->request_rewind = sink_request_rewind;
u->sink->parent.process_msg = sink_process_msg_cb;
u->sink->set_state = sink_set_state_cb;
u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->request_rewind = sink_request_rewind_cb;
u->sink->set_volume = sink_set_volume_cb;
u->sink->set_mute = sink_set_mute_cb;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
pa_sink_set_rtpoll(u->sink, master->rtpoll);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
sink_input_data.sink = u->master;
sink_input_data.sink = master;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE);
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
@ -752,11 +842,15 @@ int pa__init(pa_module*m) {
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->state_change = sink_input_state_change_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
u->sink_input->volume_changed = sink_input_volume_changed_cb;
u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->userdata = u;
pa_sink_put(u->sink);
@ -797,15 +891,20 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
if (u->sink) {
pa_sink_unlink(u->sink);
pa_sink_unref(u->sink);
}
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
if (u->sink_input) {
if (u->sink_input)
pa_sink_input_unlink(u->sink_input);
if (u->sink)
pa_sink_unlink(u->sink);
if (u->sink_input)
pa_sink_input_unref(u->sink_input);
}
if (u->sink)
pa_sink_unref(u->sink);
for (c = 0; c < u->channels; c++)
if (u->handle[c]) {

View file

@ -63,6 +63,8 @@ struct userdata {
float mute_toggle_save;
};
#define DELTA (PA_VOLUME_NORM/20)
static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) {
struct userdata *u = userdata;
char *name = NULL, *code = NULL;
@ -119,32 +121,17 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK)))
pa_log("Failed to get sink '%s'", u->sink_name);
else {
int i;
pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);
#define DELTA (PA_VOLUME_NORM/20)
pa_cvolume cv = *pa_sink_get_volume(s, FALSE);
switch (volchange) {
case UP:
for (i = 0; i < cv.channels; i++) {
if (cv.values[i] < PA_VOLUME_MAX - DELTA)
cv.values[i] += DELTA;
else
cv.values[i] = PA_VOLUME_MAX;
}
pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
pa_cvolume_inc(&cv, DELTA);
pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case DOWN:
for (i = 0; i < cv.channels; i++) {
if (cv.values[i] > DELTA)
cv.values[i] -= DELTA;
else
cv.values[i] = PA_VOLUME_MUTED;
}
pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
pa_cvolume_dec(&cv, DELTA);
pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case MUTE:
@ -156,7 +143,6 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
break;
case MUTE_TOGGLE:
pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);
break;

View file

@ -0,0 +1,784 @@
/***
This file is part of PulseAudio.
Copyright 2009 Intel Corporation
Contributor: Pierre-Louis Bossart <pierre-louis.bossart@intel.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <math.h>
#include <pulse/xmalloc.h>
#include <pulsecore/sink-input.h>
#include <pulsecore/module.h>
#include <pulsecore/modargs.h>
#include <pulsecore/namereg.h>
#include <pulsecore/log.h>
#include <pulsecore/core-util.h>
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include "module-loopback-symdef.h"
PA_MODULE_AUTHOR("Pierre-Louis Bossart");
PA_MODULE_DESCRIPTION("Loopback from source to sink");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE(
"source=<source to connect to> "
"sink=<sink to connect to> "
"adjust_time=<how often to readjust rates in s> "
"latency_msec=<latency in ms> "
"format=<sample format> "
"rate=<sample rate> "
"channels=<number of channels> "
"channel_map=<channel map>");
#define DEFAULT_LATENCY_MSEC 200
#define MEMBLOCKQ_MAXLENGTH (1024*1024*16)
#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)
struct userdata {
pa_core *core;
pa_module *module;
pa_sink_input *sink_input;
pa_source_output *source_output;
pa_asyncmsgq *asyncmsgq;
pa_memblockq *memblockq;
pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write;
pa_time_event *time_event;
pa_usec_t adjust_time;
int64_t recv_counter;
int64_t send_counter;
size_t skip;
pa_usec_t latency;
pa_bool_t in_pop;
size_t min_memblockq_length;
struct {
int64_t send_counter;
size_t source_output_buffer;
pa_usec_t source_latency;
int64_t recv_counter;
size_t sink_input_buffer;
pa_usec_t sink_latency;
size_t min_memblockq_length;
size_t max_request;
} latency_snapshot;
};
static const char* const valid_modargs[] = {
"source",
"sink",
"latency",
"format",
"rate",
"channels",
"channel_map",
NULL,
};
enum {
SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
SINK_INPUT_MESSAGE_REWIND,
SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT,
SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED
};
enum {
SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT
};
/* Called from main context */
static void teardown(struct userdata *u) {
pa_assert(u);
pa_assert_ctl_context();
if (u->sink_input)
pa_sink_input_unlink(u->sink_input);
if (u->source_output)
pa_source_output_unlink(u->source_output);
if (u->sink_input) {
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
}
if (u->source_output) {
pa_source_output_unref(u->source_output);
u->source_output = NULL;
}
}
/* Called from main context */
static void adjust_rates(struct userdata *u) {
size_t buffer, fs;
uint32_t old_rate, base_rate, new_rate;
pa_usec_t buffer_latency;
pa_assert(u);
pa_assert_ctl_context();
pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL);
pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL);
buffer =
u->latency_snapshot.sink_input_buffer +
u->latency_snapshot.source_output_buffer;
if (u->latency_snapshot.recv_counter <= u->latency_snapshot.send_counter)
buffer += (size_t) (u->latency_snapshot.send_counter - u->latency_snapshot.recv_counter);
else
buffer += PA_CLIP_SUB(buffer, (size_t) (u->latency_snapshot.recv_counter - u->latency_snapshot.send_counter));
buffer_latency = pa_bytes_to_usec(buffer, &u->sink_input->sample_spec);
pa_log_info("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms",
(double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC,
(double) buffer_latency / PA_USEC_PER_MSEC,
(double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC,
((double) u->latency_snapshot.sink_latency + buffer_latency + u->latency_snapshot.source_latency) / PA_USEC_PER_MSEC);
pa_log_info("Should buffer %zu bytes, buffered at minimum %zu bytes",
u->latency_snapshot.max_request*2,
u->latency_snapshot.min_memblockq_length);
fs = pa_frame_size(&u->sink_input->sample_spec);
old_rate = u->sink_input->sample_spec.rate;
base_rate = u->source_output->sample_spec.rate;
if (u->latency_snapshot.min_memblockq_length < u->latency_snapshot.max_request*2)
new_rate = base_rate - (((u->latency_snapshot.max_request*2 - u->latency_snapshot.min_memblockq_length) / fs) *PA_USEC_PER_SEC)/u->adjust_time;
else
new_rate = base_rate + (((u->latency_snapshot.min_memblockq_length - u->latency_snapshot.max_request*2) / fs) *PA_USEC_PER_SEC)/u->adjust_time;
pa_log_info("Old rate %lu Hz, new rate %lu Hz", (unsigned long) old_rate, (unsigned long) new_rate);
pa_sink_input_set_rate(u->sink_input, new_rate);
pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
}
/* Called from main context */
static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
struct userdata *u = userdata;
pa_assert(u);
pa_assert(a);
pa_assert(u->time_event == e);
adjust_rates(u);
}
/* Called from input thread context */
static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
struct userdata *u;
pa_memchunk copy;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
if (u->skip > chunk->length) {
u->skip -= chunk->length;
return;
}
if (u->skip > 0) {
copy = *chunk;
copy.index += u->skip;
copy.length -= u->skip;
u->skip = 0;
chunk = &copy;
}
pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, chunk, NULL);
u->send_counter += (int64_t) chunk->length;
}
/* Called from input thread context */
static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL);
u->send_counter -= (int64_t) nbytes;
}
/* Called from output thread context */
static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata;
switch (code) {
case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: {
size_t length;
length = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq);
u->latency_snapshot.send_counter = u->send_counter;
u->latency_snapshot.source_output_buffer = u->source_output->thread_info.resampler ? pa_resampler_result(u->source_output->thread_info.resampler, length) : length;
u->latency_snapshot.source_latency = pa_source_get_latency_within_thread(u->source_output->source);
return 0;
}
}
return pa_source_output_process_msg(obj, code, data, offset, chunk);
}
/* Called from output thread context */
static void source_output_attach_cb(pa_source_output *o) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
o->source->thread_info.rtpoll,
PA_RTPOLL_LATE,
u->asyncmsgq);
}
/* Called from output thread context */
static void source_output_detach_cb(pa_source_output *o) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
if (u->rtpoll_item_write) {
pa_rtpoll_item_free(u->rtpoll_item_write);
u->rtpoll_item_write = NULL;
}
}
/* Called from output thread context */
static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
pa_assert_se(u = o->userdata);
if (PA_SOURCE_OUTPUT_IS_LINKED(state) && o->thread_info.state == PA_SOURCE_OUTPUT_INIT) {
u->skip = pa_usec_to_bytes(PA_CLIP_SUB(pa_source_get_latency_within_thread(o->source),
u->latency),
&o->sample_spec);
pa_log_info("Skipping %lu bytes", (unsigned long) u->skip);
}
}
/* Called from main thread */
static void source_output_kill_cb(pa_source_output *o) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_assert_ctl_context();
pa_assert_se(u = o->userdata);
teardown(u);
pa_module_unload_request(u->module, TRUE);
}
/* Called from main thread */
static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) {
struct userdata *u;
pa_source_output_assert_ref(o);
pa_assert_ctl_context();
pa_assert_se(u = o->userdata);
return dest != u->sink_input->sink->monitor_source;
}
/* Called from main thread */
static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
pa_proplist *p;
const char *n;
struct userdata *u;
pa_source_output_assert_ref(o);
pa_assert_ctl_context();
pa_assert_se(u = o->userdata);
p = pa_proplist_new();
pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback of %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION)));
if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME)))
pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n);
pa_sink_input_update_proplist(u->sink_input, PA_UPDATE_REPLACE, p);
pa_proplist_free(p);
}
/* Called from output thread context */
static void update_min_memblockq_length(struct userdata *u) {
size_t length;
pa_assert(u);
pa_sink_input_assert_io_context(u->sink_input);
length = pa_memblockq_get_length(u->memblockq);
if (u->min_memblockq_length == (size_t) -1 ||
length < u->min_memblockq_length)
u->min_memblockq_length = length;
}
/* Called from output thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_sink_input_assert_io_context(i);
pa_assert_se(u = i->userdata);
pa_assert(chunk);
u->in_pop = TRUE;
while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
;
u->in_pop = FALSE;
if (pa_memblockq_peek(u->memblockq, chunk) < 0) {
pa_log_info("Coud not peek into queue");
return -1;
}
chunk->length = PA_MIN(chunk->length, nbytes);
pa_memblockq_drop(u->memblockq, chunk->length);
update_min_memblockq_length(u);
return 0;
}
/* Called from output thread context */
static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_sink_input_assert_io_context(i);
pa_assert_se(u = i->userdata);
pa_memblockq_rewind(u->memblockq, nbytes);
}
/* Called from output thread context */
static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK_INPUT(obj)->userdata;
switch (code) {
case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
pa_usec_t *r = data;
pa_sink_input_assert_io_context(u->sink_input);
*r = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->sink_input->sample_spec);
/* Fall through, the default handler will add in the extra
* latency added by the resampler */
break;
}
case SINK_INPUT_MESSAGE_POST:
pa_sink_input_assert_io_context(u->sink_input);
if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state))
pa_memblockq_push_align(u->memblockq, chunk);
else
pa_memblockq_flush_write(u->memblockq);
update_min_memblockq_length(u);
/* Is this the end of an underrun? Then let's start things
* right-away */
if (!u->in_pop &&
u->sink_input->thread_info.underrun_for > 0 &&
pa_memblockq_is_readable(u->memblockq)) {
pa_log_debug("Requesting rewind due to end of underrun.");
pa_sink_input_request_rewind(u->sink_input,
(size_t) (u->sink_input->thread_info.underrun_for == (size_t) -1 ? 0 : u->sink_input->thread_info.underrun_for),
FALSE, TRUE, FALSE);
}
u->recv_counter += (int64_t) chunk->length;
return 0;
case SINK_INPUT_MESSAGE_REWIND:
pa_sink_input_assert_io_context(u->sink_input);
if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state))
pa_memblockq_seek(u->memblockq, -offset, PA_SEEK_RELATIVE, TRUE);
else
pa_memblockq_flush_write(u->memblockq);
u->recv_counter -= offset;
update_min_memblockq_length(u);
return 0;
case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: {
size_t length;
update_min_memblockq_length(u);
length = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq);
u->latency_snapshot.recv_counter = u->recv_counter;
u->latency_snapshot.sink_input_buffer =
pa_memblockq_get_length(u->memblockq) +
(u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, length) : length);
u->latency_snapshot.sink_latency = pa_sink_get_latency_within_thread(u->sink_input->sink);
u->latency_snapshot.max_request = pa_sink_input_get_max_request(u->sink_input);
u->latency_snapshot.min_memblockq_length = u->min_memblockq_length;
u->min_memblockq_length = (size_t) -1;
return 0;
}
case SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED: {
/* This message is sent from the IO thread to the main
* thread! So don't be confused. All the user cases above
* are executed in thread context, but this one is not! */
pa_assert_ctl_context();
adjust_rates(u);
return 0;
}
}
return pa_sink_input_process_msg(obj, code, data, offset, chunk);
}
/* Called from output thread context */
static void sink_input_attach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_sink_input_assert_io_context(i);
pa_assert_se(u = i->userdata);
u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
i->sink->thread_info.rtpoll,
PA_RTPOLL_LATE,
u->asyncmsgq);
pa_memblockq_set_prebuf(u->memblockq, pa_sink_input_get_max_request(i)*2);
pa_memblockq_set_maxrewind(u->memblockq, pa_sink_input_get_max_rewind(i));
u->min_memblockq_length = (size_t) -1;
}
/* Called from output thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_sink_input_assert_io_context(i);
pa_assert_se(u = i->userdata);
if (u->rtpoll_item_read) {
pa_rtpoll_item_free(u->rtpoll_item_read);
u->rtpoll_item_read = NULL;
}
}
/* Called from output thread context */
static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_sink_input_assert_io_context(i);
pa_assert_se(u = i->userdata);
pa_memblockq_set_maxrewind(u->memblockq, nbytes);
}
/* Called from output thread context */
static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_sink_input_assert_io_context(i);
pa_assert_se(u = i->userdata);
pa_memblockq_set_prebuf(u->memblockq, nbytes*2);
pa_log_info("Max request changed");
pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED, NULL, 0, NULL, NULL);
}
/* Called from main thread */
static void sink_input_kill_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_ctl_context();
pa_assert_se(u = i->userdata);
teardown(u);
pa_module_unload_request(u->module, TRUE);
}
/* Called from main thread */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_proplist *p;
const char *n;
pa_sink_input_assert_ref(i);
pa_assert_ctl_context();
pa_assert_se(u = i->userdata);
p = pa_proplist_new();
pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback to %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION)));
if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME)))
pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n);
pa_source_output_update_proplist(u->source_output, PA_UPDATE_REPLACE, p);
pa_proplist_free(p);
}
/* Called from main thread */
static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_ctl_context();
pa_assert_se(u = i->userdata);
if (!u->source_output->source->monitor_of)
return TRUE;
return dest != u->source_output->source->monitor_of;
}
int pa__init(pa_module *m) {
pa_modargs *ma = NULL;
struct userdata *u;
pa_sink *sink;
pa_sink_input_new_data sink_input_data;
pa_source *source;
pa_source_output_new_data source_output_data;
uint32_t latency_msec;
pa_sample_spec ss;
pa_channel_map map;
pa_memchunk silence;
uint32_t adjust_time_sec;
const char *n;
pa_assert(m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments");
goto fail;
}
if (!(source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE))) {
pa_log("No such source.");
goto fail;
}
if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK))) {
pa_log("No such sink.");
goto fail;
}
ss = sink->sample_spec;
map = sink->channel_map;
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
pa_log("Invalid sample format specification or channel map");
goto fail;
}
latency_msec = DEFAULT_LATENCY_MSEC;
if (pa_modargs_get_value_u32(ma, "latency_msec", &latency_msec) < 0 || latency_msec < 1 || latency_msec > 2000) {
pa_log("Invalid latency specification");
goto fail;
}
m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
u->latency = (pa_usec_t) latency_msec * PA_USEC_PER_MSEC;
adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
pa_log("Failed to parse adjust_time value");
goto fail;
}
if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
else
u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
sink_input_data.sink = sink;
pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Loopback of %s",
pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
if ((n = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)))
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ICON_NAME, n);
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "abstract");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE;
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
goto fail;
u->sink_input->parent.process_msg = sink_input_process_msg_cb;
u->sink_input->pop = sink_input_pop_cb;
u->sink_input->process_rewind = sink_input_process_rewind_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
u->sink_input->userdata = u;
pa_sink_input_set_requested_latency(u->sink_input, u->latency/3);
pa_source_output_new_data_init(&source_output_data);
source_output_data.driver = __FILE__;
source_output_data.module = m;
source_output_data.source = source;
pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Loopback to %s",
pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
if ((n = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)))
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ICON_NAME, n);
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "abstract");
pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
pa_source_output_new(&u->source_output, m->core, &source_output_data);
pa_source_output_new_data_done(&source_output_data);
if (!u->source_output)
goto fail;
u->source_output->parent.process_msg = source_output_process_msg_cb;
u->source_output->push = source_output_push_cb;
u->source_output->process_rewind = source_output_process_rewind_cb;
u->source_output->kill = source_output_kill_cb;
u->source_output->attach = source_output_attach_cb;
u->source_output->detach = source_output_detach_cb;
u->source_output->state_change = source_output_state_change_cb;
u->source_output->may_move_to = source_output_may_move_to_cb;
u->source_output->moving = source_output_moving_cb;
u->source_output->userdata = u;
pa_source_output_set_requested_latency(u->source_output, u->latency/3);
pa_sink_input_get_silence(u->sink_input, &silence);
u->memblockq = pa_memblockq_new(
0, /* idx */
MEMBLOCKQ_MAXLENGTH, /* maxlength */
MEMBLOCKQ_MAXLENGTH, /* tlength */
pa_frame_size(&ss), /* base */
0, /* prebuf */
0, /* minreq */
0, /* maxrewind */
&silence); /* silence frame */
pa_memblock_unref(silence.memblock);
u->asyncmsgq = pa_asyncmsgq_new(0);
pa_sink_input_put(u->sink_input);
pa_source_output_put(u->source_output);
if (u->adjust_time > 0)
u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(m);
return -1;
}
void pa__done(pa_module*m) {
struct userdata *u;
pa_assert(m);
if (!(u = m->userdata))
return;
teardown(u);
if (u->memblockq)
pa_memblockq_free(u->memblockq);
if (u->asyncmsgq)
pa_asyncmsgq_unref(u->asyncmsgq);
if (u->time_event)
u->core->mainloop->time_free(u->time_event);
pa_xfree(u);
}

View file

@ -216,7 +216,7 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
pa_cvolume cv;
pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume);
pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
pa_sink_input_set_volume(si, &cv, TRUE, TRUE);
pa_sink_input_set_volume(si, &cv, TRUE, FALSE);
}
}
}
@ -243,6 +243,9 @@ int pa__init(pa_module*m) {
if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
goto fail;
/* FIXME: Doing this asynchronously is just broken. This needs to
* use a hook! */
u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
pa_modargs_free(ma);

View file

@ -65,6 +65,8 @@ struct userdata {
pa_module *module;
};
#define DELTA (PA_VOLUME_NORM/20)
static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) {
struct userdata *u = userdata;
@ -85,14 +87,27 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
}
if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) {
enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID;
enum {
INVALID,
UP,
DOWN,
MUTE_TOGGLE
} volchange = INVALID;
pa_log_debug("Key code=%u, value=%u", ev.code, ev.value);
switch (ev.code) {
case KEY_VOLUMEDOWN: volchange = DOWN; break;
case KEY_VOLUMEUP: volchange = UP; break;
case KEY_MUTE: volchange = MUTE_TOGGLE; break;
case KEY_VOLUMEDOWN:
volchange = DOWN;
break;
case KEY_VOLUMEUP:
volchange = UP;
break;
case KEY_MUTE:
volchange = MUTE_TOGGLE;
break;
}
if (volchange != INVALID) {
@ -101,36 +116,20 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK)))
pa_log("Failed to get sink '%s'", u->sink_name);
else {
int i;
pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);
#define DELTA (PA_VOLUME_NORM/20)
pa_cvolume cv = *pa_sink_get_volume(s, FALSE);
switch (volchange) {
case UP:
for (i = 0; i < cv.channels; i++) {
if (cv.values[i] < PA_VOLUME_MAX - DELTA)
cv.values[i] += DELTA;
else
cv.values[i] = PA_VOLUME_MAX;
}
pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
pa_cvolume_inc(&cv, DELTA);
pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case DOWN:
for (i = 0; i < cv.channels; i++) {
if (cv.values[i] > DELTA)
cv.values[i] -= DELTA;
else
cv.values[i] = PA_VOLUME_MUTED;
}
pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
pa_cvolume_dec(&cv, DELTA);
pa_sink_set_volume(s, &cv, TRUE, TRUE);
break;
case MUTE_TOGGLE:
pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);
break;

View file

@ -35,6 +35,7 @@
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/xmalloc.h>
#include <pulse/i18n.h>
#include <pulsecore/macro.h>
#include <pulsecore/sink.h>
@ -51,7 +52,7 @@
#include "module-null-sink-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("Clocked NULL sink");
PA_MODULE_DESCRIPTION(_("Clocked NULL sink"));
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE(
@ -287,7 +288,7 @@ int pa__init(pa_module*m) {
pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
pa_sink_new_data_set_sample_spec(&data, &ss);
pa_sink_new_data_set_channel_map(&data, &map);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output"));
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_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) {

View file

@ -122,7 +122,7 @@ static int process_render(struct userdata *u) {
pa_assert(u);
if (u->memchunk.length <= 0)
pa_sink_render(u->sink, PIPE_BUF, &u->memchunk);
pa_sink_render(u->sink, pa_pipe_buf(u->fd), &u->memchunk);
pa_assert(u->memchunk.length > 0);
@ -299,8 +299,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_max_request(u->sink, PIPE_BUF);
pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(PIPE_BUF, &u->sink->sample_spec));
pa_sink_set_max_request(u->sink, pa_pipe_buf(u->fd));
pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(pa_pipe_buf(u->fd), &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);

View file

@ -142,7 +142,7 @@ static void thread_func(void *userdata) {
void *p;
if (!u->memchunk.memblock) {
u->memchunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF);
u->memchunk.memblock = pa_memblock_new(u->core->mempool, pa_pipe_buf(u->fd));
u->memchunk.index = u->memchunk.length = 0;
}

View file

@ -57,35 +57,68 @@ struct userdata {
pa_hook_slot *sink_input_fixate_hook_slot;
};
static int parse_pos(const char *pos, double *f) {
if (pa_atod(pos, f) < 0) {
pa_log_warn("Failed to parse hpos/vpos property '%s'.", pos);
return -1;
}
if (*f < 0.0 || *f > 1.0) {
pa_log_warn("Property hpos/vpos out of range %0.2f", *f);
return -1;
}
return 0;
}
static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *data, struct userdata *u) {
const char *hpos;
const char *hpos, *vpos, *role;
double f;
char t[PA_CVOLUME_SNPRINT_MAX];
pa_cvolume v;
pa_assert(data);
if (!(role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE)))
return PA_HOOK_OK;
if (!pa_streq(role, "event"))
return PA_HOOK_OK;
if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS)))
hpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_HPOS);
if (!(vpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_VPOS)))
vpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_VPOS);
if (!hpos && !vpos)
return PA_HOOK_OK;
if (pa_atod(hpos, &f) < 0) {
pa_log_warn("Failed to parse "PA_PROP_EVENT_MOUSE_HPOS" property '%s'.", hpos);
pa_cvolume_reset(&v, data->sink->sample_spec.channels);
if (hpos) {
if (parse_pos(hpos, &f) < 0)
return PA_HOOK_OK;
if (pa_channel_map_can_balance(&data->sink->channel_map)) {
pa_log_debug("Positioning event sound '%s' horizontally at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
pa_cvolume_set_balance(&v, &data->sink->channel_map, f*2.0-1.0);
}
}
if (f < 0.0 || f > 1.0) {
pa_log_warn("Property "PA_PROP_EVENT_MOUSE_HPOS" out of range %0.2f", f);
if (vpos) {
if (parse_pos(vpos, &f) < 0)
return PA_HOOK_OK;
if (pa_channel_map_can_fade(&data->sink->channel_map)) {
pa_log_debug("Positioning event sound '%s' vertically at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
pa_cvolume_set_fade(&v, &data->sink->channel_map, f*2.0-1.0);
}
}
pa_log_debug("Positioning event sound '%s' at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
pa_cvolume_reset(&v, data->sample_spec.channels);
pa_cvolume_set_balance(&v, &data->channel_map, f*2.0-1.0);
pa_log_debug("Final volume factor %s.", pa_cvolume_snprint(t, sizeof(t), &v));
pa_sink_input_new_data_apply_volume_factor(data, &v);
pa_sink_input_new_data_apply_volume_factor_sink(data, &v);
return PA_HOOK_OK;
}

View file

@ -1,7 +1,7 @@
/***
This file is part of PulseAudio.
Copyright 2004-2008 Lennart Poettering
Copyright 2004-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
@ -48,17 +48,18 @@ PA_MODULE_USAGE(
"master=<name of sink to remap> "
"master_channel_map=<channel map> "
"format=<sample format> "
"channels=<number of channels> "
"rate=<sample rate> "
"channels=<number of channels> "
"channel_map=<channel map> "
"remix=<remix channels?>");
struct userdata {
pa_core *core;
pa_module *module;
pa_sink *sink, *master;
pa_sink *sink;
pa_sink_input *sink_input;
pa_bool_t auto_desc;
};
static const char* const valid_modargs[] = {
@ -80,19 +81,24 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
switch (code) {
case PA_SINK_MESSAGE_GET_LATENCY: {
pa_usec_t usec = 0;
case PA_SINK_MESSAGE_GET_LATENCY:
/* Get the latency of the master sink */
if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
usec = 0;
/* Add the latency internal to our sink input on top */
usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec);
*((pa_usec_t*) data) = usec;
/* The sink is _put() before the sink input is, so let's
* make sure we don't access it yet */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
*((pa_usec_t*) data) = 0;
return 0;
}
*((pa_usec_t*) data) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread(u->sink_input->sink) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
return 0;
}
return pa_sink_process_msg(o, code, data, offset, chunk);
@ -105,12 +111,11 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (PA_SINK_IS_LINKED(state) &&
u->sink_input &&
PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
if (!PA_SINK_IS_LINKED(state) ||
!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
return 0;
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
return 0;
}
@ -121,6 +126,10 @@ static void sink_request_rewind(pa_sink *s) {
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE);
}
@ -131,6 +140,10 @@ static void sink_update_requested_latency(pa_sink *s) {
pa_sink_assert_ref(s);
pa_assert_se(u = s->userdata);
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
!PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
return;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread(
u->sink_input,
@ -145,8 +158,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk
pa_assert(chunk);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
return -1;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind(u->sink, 0);
pa_sink_render(u->sink, nbytes, chunk);
return 0;
@ -160,9 +173,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state))
return;
if (u->sink->thread_info.rewind_nbytes > 0) {
amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
u->sink->thread_info.rewind_nbytes = 0;
@ -178,9 +188,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
@ -191,9 +198,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
@ -204,12 +208,19 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
}
/* Called from I/O thread context */
static void sink_input_detach_cb(pa_sink_input *i) {
struct userdata *u;
@ -217,11 +228,8 @@ static void sink_input_detach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_detach_within_thread(u->sink);
pa_sink_set_asyncmsgq(u->sink, NULL);
pa_sink_set_rtpoll(u->sink, NULL);
}
@ -232,14 +240,13 @@ static void sink_input_attach_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state))
return;
pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
pa_sink_attach_within_thread(u->sink);
pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency);
}
/* Called from main context */
@ -249,13 +256,17 @@ static void sink_input_kill_cb(pa_sink_input *i) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_sink_unlink(u->sink);
/* The order here matters! We first kill the sink input, followed
* by the sink. That means the sink callbacks must be protected
* against an unconnected sink input! */
pa_sink_input_unlink(u->sink_input);
pa_sink_unlink(u->sink);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_sink_unref(u->sink);
u->sink = NULL;
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_module_unload_request(u->module, TRUE);
}
@ -286,12 +297,37 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
return u->sink != dest;
}
/* Called from main context */
static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
struct userdata *u;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (dest) {
pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
} else
pa_sink_set_asyncmsgq(u->sink, NULL);
if (u->auto_desc && dest) {
const char *k;
pa_proplist *pl;
pl = pa_proplist_new();
k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name);
pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
pa_proplist_free(pl);
}
}
int pa__init(pa_module*m) {
struct userdata *u;
pa_sample_spec ss;
pa_channel_map sink_map, stream_map;
pa_modargs *ma;
const char *k;
pa_sink *master;
pa_sink_input_new_data sink_input_data;
pa_sink_new_data sink_data;
@ -336,12 +372,8 @@ int pa__init(pa_module*m) {
}
u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
m->userdata = u;
u->master = master;
u->sink = NULL;
u->sink_input = NULL;
/* Create sink */
pa_sink_new_data_init(&sink_data);
@ -351,8 +383,6 @@ int pa__init(pa_module*m) {
sink_data.name = pa_sprintf_malloc("%s.remapped", master->name);
pa_sink_new_data_set_sample_spec(&sink_data, &ss);
pa_sink_new_data_set_channel_map(&sink_data, &sink_map);
k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
@ -362,7 +392,14 @@ int pa__init(pa_module*m) {
goto fail;
}
u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY);
if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
const char *k;
k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name);
}
u->sink = pa_sink_new(m->core, &sink_data, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
@ -377,19 +414,19 @@ int pa__init(pa_module*m) {
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
pa_sink_set_rtpoll(u->sink, master->rtpoll);
/* Create sink input */
pa_sink_input_new_data_init(&sink_input_data);
sink_input_data.driver = __FILE__;
sink_input_data.module = m;
sink_input_data.sink = u->master;
sink_input_data.sink = master;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map);
sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX);
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE | (remix ? 0 : PA_SINK_INPUT_NO_REMIX));
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
pa_sink_input_new_data_done(&sink_input_data);
if (!u->sink_input)
@ -400,11 +437,13 @@ int pa__init(pa_module*m) {
u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
u->sink_input->update_max_request = sink_input_update_max_request_cb;
u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
u->sink_input->attach = sink_input_attach_cb;
u->sink_input->detach = sink_input_detach_cb;
u->sink_input->kill = sink_input_kill_cb;
u->sink_input->state_change = sink_input_state_change_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
u->sink_input->userdata = u;
pa_sink_put(u->sink);
@ -440,15 +479,20 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
if (u->sink) {
pa_sink_unlink(u->sink);
pa_sink_unref(u->sink);
}
/* See comments in sink_input_kill_cb() above regarding
* destruction order! */
if (u->sink_input) {
if (u->sink_input)
pa_sink_input_unlink(u->sink_input);
if (u->sink)
pa_sink_unlink(u->sink);
if (u->sink_input)
pa_sink_input_unref(u->sink_input);
}
if (u->sink)
pa_sink_unref(u->sink);
pa_xfree(u);
}

View file

@ -45,13 +45,46 @@ static const char* const valid_modargs[] = {
};
struct userdata {
pa_hook_slot *sink_slot, *source_slot;
pa_hook_slot
*sink_unlink_slot,
*source_unlink_slot,
*sink_input_move_fail_slot,
*source_output_move_fail_slot;
};
static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
static pa_sink* find_evacuation_sink(pa_core *c, pa_sink_input *i, pa_sink *skip) {
pa_sink *target, *def;
uint32_t idx;
pa_assert(c);
pa_assert(i);
def = pa_namereg_get_default_sink(c);
if (def && def != skip && pa_sink_input_may_move_to(i, def))
return def;
PA_IDXSET_FOREACH(target, c->sinks, idx) {
if (target == def)
continue;
if (target == skip)
continue;
if (!PA_SINK_IS_LINKED(pa_sink_get_state(target)))
continue;
if (pa_sink_input_may_move_to(i, target))
return target;
}
pa_log_debug("No evacuation sink found.");
return NULL;
}
static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
pa_sink_input *i;
uint32_t idx;
pa_sink *target;
pa_assert(c);
pa_assert(sink);
@ -65,21 +98,12 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user
return PA_HOOK_OK;
}
if (!(target = pa_namereg_get_default_sink(c)) || target == sink) {
PA_IDXSET_FOREACH(target, c->sinks, idx)
if (target != sink)
break;
if (!target) {
pa_log_debug("No evacuation sink found.");
return PA_HOOK_OK;
}
}
pa_assert(target != sink);
PA_IDXSET_FOREACH(i, sink->inputs, idx) {
pa_sink *target;
if (!(target = find_evacuation_sink(c, i, sink)))
continue;
if (pa_sink_input_move_to(i, target, FALSE) < 0)
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);
@ -91,9 +115,66 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user
return PA_HOOK_OK;
}
static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) {
static pa_hook_result_t sink_input_move_fail_hook_callback(pa_core *c, pa_sink_input *i, void *userdata) {
pa_sink *target;
pa_assert(c);
pa_assert(i);
/* There's no point in doing anything if the core is shut down anyway */
if (c->state == PA_CORE_SHUTDOWN)
return PA_HOOK_OK;
if (!(target = find_evacuation_sink(c, i, NULL)))
return PA_HOOK_OK;
if (pa_sink_input_finish_move(i, target, FALSE) < 0) {
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);
return PA_HOOK_OK;
} else {
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_STOP;
}
}
static pa_source* find_evacuation_source(pa_core *c, pa_source_output *o, pa_source *skip) {
pa_source *target, *def;
uint32_t idx;
pa_assert(c);
pa_assert(o);
def = pa_namereg_get_default_source(c);
if (def && def != skip && pa_source_output_may_move_to(o, def))
return def;
PA_IDXSET_FOREACH(target, c->sources, idx) {
if (target == def)
continue;
if (target == skip)
continue;
if (!target->monitor_of != !skip->monitor_of)
continue;
if (!PA_SOURCE_IS_LINKED(pa_source_get_state(target)))
continue;
if (pa_source_output_may_move_to(o, target))
return target;
}
pa_log_debug("No evacuation source found.");
return NULL;
}
static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, void* userdata) {
pa_source_output *o;
pa_source *target;
uint32_t idx;
pa_assert(c);
@ -108,21 +189,12 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void
return PA_HOOK_OK;
}
if (!(target = pa_namereg_get_default_source(c)) || target == source) {
PA_IDXSET_FOREACH(target, c->sources, idx)
if (target != source && !target->monitor_of == !source->monitor_of)
break;
if (!target) {
pa_log_info("No evacuation source found.");
return PA_HOOK_OK;
}
}
pa_assert(target != source);
PA_IDXSET_FOREACH(o, source->outputs, idx) {
pa_source *target;
if (!(target = find_evacuation_source(c, o, source)))
continue;
if (pa_source_output_move_to(o, target, FALSE) < 0)
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);
@ -134,6 +206,31 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void
return PA_HOOK_OK;
}
static pa_hook_result_t source_output_move_fail_hook_callback(pa_core *c, pa_source_output *i, void *userdata) {
pa_source *target;
pa_assert(c);
pa_assert(i);
/* There's no point in doing anything if the core is shut down anyway */
if (c->state == PA_CORE_SHUTDOWN)
return PA_HOOK_OK;
if (!(target = find_evacuation_source(c, i, NULL)))
return PA_HOOK_OK;
if (pa_source_output_finish_move(i, target, FALSE) < 0) {
pa_log_info("Failed to move source input %u \"%s\" to %s.", i->index,
pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name);
return PA_HOOK_OK;
} else {
pa_log_info("Sucessfully moved source input %u \"%s\" to %s.", i->index,
pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name);
return PA_HOOK_STOP;
}
}
int pa__init(pa_module*m) {
pa_modargs *ma;
struct userdata *u;
@ -148,8 +245,11 @@ int pa__init(pa_module*m) {
m->userdata = u = pa_xnew(struct userdata, 1);
/* A little bit later than module-stream-restore, module-intended-roles... */
u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_hook_callback, u);
u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) source_hook_callback, u);
u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_unlink_hook_callback, u);
u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) source_unlink_hook_callback, u);
u->sink_input_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_input_move_fail_hook_callback, u);
u->source_output_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) source_output_move_fail_hook_callback, u);
pa_modargs_free(ma);
return 0;
@ -163,10 +263,15 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
return;
if (u->sink_slot)
pa_hook_slot_free(u->sink_slot);
if (u->source_slot)
pa_hook_slot_free(u->source_slot);
if (u->sink_unlink_slot)
pa_hook_slot_free(u->sink_unlink_slot);
if (u->source_unlink_slot)
pa_hook_slot_free(u->source_unlink_slot);
if (u->sink_input_move_fail_slot)
pa_hook_slot_free(u->sink_input_move_fail_slot);
if (u->source_output_move_fail_slot)
pa_hook_slot_free(u->source_output_move_fail_slot);
pa_xfree(u);
}

View file

@ -163,7 +163,7 @@ int pa__init(pa_module*m) {
pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency);
pa_sink_input_new_data_set_sample_spec(&data, &ss);
pa_sink_input_new(&u->sink_input, m->core, &data, 0);
pa_sink_input_new(&u->sink_input, m->core, &data);
pa_sink_input_new_data_done(&data);
if (!u->sink_input)

View file

@ -60,6 +60,7 @@
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/thread.h>
#include <pulsecore/time-smoother.h>
#include "module-solaris-symdef.h"
@ -110,6 +111,8 @@ struct userdata {
uint32_t prev_playback_samples, prev_record_samples;
int32_t minimum_request;
pa_smoother *smoother;
};
static const char* const valid_modargs[] = {
@ -133,6 +136,9 @@ static const char* const valid_modargs[] = {
#define MAX_RENDER_HZ (300)
/* This render rate limit imposes a minimum latency, but without it we waste too much CPU time. */
#define MAX_BUFFER_SIZE (128 * 1024)
/* An attempt to buffer more than 128 KB causes write() to fail with errno == EAGAIN. */
static uint64_t get_playback_buffered_bytes(struct userdata *u) {
audio_info_t info;
uint64_t played_bytes;
@ -145,7 +151,12 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) {
/* Handle wrap-around of the device's sample counter, which is a uint_32. */
if (u->prev_playback_samples > info.play.samples) {
/* Unfortunately info.play.samples can sometimes go backwards, even before it wraps! */
/*
* Unfortunately info.play.samples can sometimes go backwards, even before it wraps!
* The bug seems to be absent on Solaris x86 nv117 with audio810 driver, at least on this (UP) machine.
* The bug is present on a different (SMP) machine running Solaris x86 nv103 with audioens driver.
* An earlier revision of this file mentions the same bug independently (unknown configuration).
*/
if (u->prev_playback_samples + info.play.samples < 240000) {
++u->play_samples_msw;
} else {
@ -155,6 +166,8 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) {
u->prev_playback_samples = info.play.samples;
played_bytes = (((uint64_t)u->play_samples_msw << 32) + info.play.samples) * u->frame_size;
pa_smoother_put(u->smoother, pa_rtclock_now(), pa_bytes_to_usec(played_bytes, &u->sink->sample_spec));
return u->written_bytes - played_bytes;
}
@ -387,6 +400,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
pa_smoother_pause(u->smoother, pa_rtclock_now());
if (!u->source || u->source_suspended) {
if (suspend(u) < 0)
return -1;
@ -398,6 +413,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
case PA_SINK_RUNNING:
if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);
if (!u->source || u->source_suspended) {
if (unsuspend(u) < 0)
return -1;
@ -479,7 +496,7 @@ static void sink_set_volume(pa_sink *s) {
if (u->fd >= 0) {
AUDIO_INITINFO(&info);
info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
info.play.gain = pa_cvolume_max(&s->real_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
assert(info.play.gain <= AUDIO_MAX_GAIN);
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
@ -501,8 +518,7 @@ static void sink_get_volume(pa_sink *s) {
if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
else
pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels,
info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
pa_cvolume_set(&s->real_volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
}
}
@ -515,7 +531,7 @@ static void source_set_volume(pa_source *s) {
if (u->fd >= 0) {
AUDIO_INITINFO(&info);
info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
info.play.gain = pa_cvolume_max(&s->volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
assert(info.play.gain <= AUDIO_MAX_GAIN);
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
@ -537,8 +553,7 @@ static void source_get_volume(pa_source *s) {
if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
else
pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels,
info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
pa_cvolume_set(&s->volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
}
}
@ -585,6 +600,10 @@ static void process_rewind(struct userdata *u) {
pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);
rewind_nbytes = PA_MIN(u->memchunk.length, rewind_nbytes);
u->memchunk.length -= rewind_nbytes;
if (u->memchunk.length <= 0 && u->memchunk.memblock) {
pa_memblock_unref(u->memchunk.memblock);
pa_memchunk_reset(&u->memchunk);
}
pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes);
}
@ -606,11 +625,13 @@ static void thread_func(void *userdata) {
pa_thread_mq_install(&u->thread_mq);
pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
for (;;) {
/* Render some data and write it to the dsp */
if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
pa_usec_t xtime0;
pa_usec_t xtime0, ysleep_interval, xsleep_interval;
uint64_t buffered_bytes;
if (u->sink->thread_info.rewind_requested)
@ -629,12 +650,15 @@ static void thread_func(void *userdata) {
info.play.error = 0;
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
}
for (;;) {
void *p;
ssize_t w;
size_t len;
int write_type = 1;
/*
* Since we cannot modify the size of the output buffer we fake it
@ -651,29 +675,23 @@ static void thread_func(void *userdata) {
if (len < (size_t) u->minimum_request)
break;
if (u->memchunk.length < len)
if (!u->memchunk.length)
pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk);
len = PA_MIN(u->memchunk.length, len);
p = pa_memblock_acquire(u->memchunk.memblock);
w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL);
w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, len, &write_type);
pa_memblock_release(u->memchunk.memblock);
if (w <= 0) {
switch (errno) {
case EINTR:
if (errno == EINTR) {
continue;
case EAGAIN:
/* If the buffer_size is too big, we get EAGAIN. Avoiding that limit by trial and error
* is not ideal, but I don't know how to get the system to tell me what the limit is.
*/
u->buffer_size = u->buffer_size * 18 / 25;
u->buffer_size -= u->buffer_size % u->frame_size;
u->buffer_size = PA_MAX(u->buffer_size, 2 * u->minimum_request);
pa_sink_set_max_request_within_thread(u->sink, u->buffer_size);
pa_sink_set_max_rewind_within_thread(u->sink, u->buffer_size);
pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes);
} else if (errno == EAGAIN) {
/* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */
pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes);
break;
default:
} else {
pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
goto fail;
}
@ -681,9 +699,8 @@ static void thread_func(void *userdata) {
pa_assert(w % u->frame_size == 0);
u->written_bytes += w;
u->memchunk.length -= w;
u->memchunk.index += w;
u->memchunk.length -= w;
if (u->memchunk.length <= 0) {
pa_memblock_unref(u->memchunk.memblock);
pa_memchunk_reset(&u->memchunk);
@ -691,7 +708,9 @@ static void thread_func(void *userdata) {
}
}
pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec));
ysleep_interval = pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec);
xsleep_interval = pa_smoother_translate(u->smoother, xtime0, ysleep_interval);
pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + PA_MIN(xsleep_interval, ysleep_interval));
} else
pa_rtpoll_set_timer_disabled(u->rtpoll);
@ -797,7 +816,7 @@ static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void
pa_log_debug("caught signal");
if (u->sink) {
pa_sink_get_volume(u->sink, TRUE, FALSE);
pa_sink_get_volume(u->sink, TRUE);
pa_sink_get_mute(u->sink, TRUE);
}
@ -812,7 +831,7 @@ int pa__init(pa_module *m) {
pa_channel_map map;
pa_modargs *ma = NULL;
uint32_t buffer_length_msec;
int fd;
int fd = -1;
pa_sink_new_data sink_new_data;
pa_source_new_data source_new_data;
char const *name;
@ -838,6 +857,9 @@ int pa__init(pa_module *m) {
u = pa_xnew0(struct userdata, 1);
if (!(u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC * 2, TRUE, TRUE, 10, pa_rtclock_now(), TRUE)))
goto fail;
/*
* For a process (or several processes) to use the same audio device for both
* record and playback at the same time, the device's mixer must be enabled.
@ -861,7 +883,13 @@ int pa__init(pa_module *m) {
}
u->buffer_size = pa_usec_to_bytes(1000 * buffer_length_msec, &ss);
if (u->buffer_size < 2 * u->minimum_request) {
pa_log("supplied buffer size argument is too small");
pa_log("buffer_length argument cannot be smaller than %u",
(unsigned)(pa_bytes_to_usec(2 * u->minimum_request, &ss) / 1000));
goto fail;
}
if (u->buffer_size > MAX_BUFFER_SIZE) {
pa_log("buffer_length argument cannot be greater than %u",
(unsigned)(pa_bytes_to_usec(MAX_BUFFER_SIZE, &ss) / 1000));
goto fail;
}
@ -924,6 +952,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);
pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->source->sample_spec));
u->source->get_volume = source_get_volume;
u->source->set_volume = source_set_volume;
@ -966,15 +995,15 @@ 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->buffer_size, &u->sink->sample_spec));
pa_sink_set_max_request(u->sink, u->buffer_size);
pa_sink_set_max_rewind(u->sink, u->buffer_size);
u->sink->get_volume = sink_get_volume;
u->sink->set_volume = sink_set_volume;
u->sink->get_mute = sink_get_mute;
u->sink->set_mute = sink_set_mute;
u->sink->refresh_volume = u->sink->refresh_muted = TRUE;
pa_sink_set_max_request(u->sink, u->buffer_size);
pa_sink_set_max_rewind(u->sink, u->buffer_size);
} else
u->sink = NULL;
@ -1075,6 +1104,9 @@ void pa__done(pa_module *m) {
if (u->fd >= 0)
close(u->fd);
if (u->smoother)
pa_smoother_free(u->smoother);
pa_xfree(u->device_name);
pa_xfree(u);

View file

@ -102,15 +102,16 @@ struct userdata {
pa_idxset *subscribed;
};
#define ENTRY_VERSION 2
#define ENTRY_VERSION 3
struct entry {
uint8_t version;
pa_bool_t muted_valid:1, volume_valid:1, device_valid:1;
pa_bool_t muted_valid:1, volume_valid:1, device_valid:1, card_valid:1;
pa_bool_t muted:1;
pa_channel_map channel_map;
pa_cvolume volume;
char device[PA_NAME_MAX];
char card[PA_NAME_MAX];
} PA_GCC_PACKED;
enum {
@ -196,11 +197,21 @@ static struct entry* read_entry(struct userdata *u, const char *name) {
goto fail;
}
if (!memchr(e->card, 0, sizeof(e->card))) {
pa_log_warn("Database contains entry for stream %s with missing NUL byte in card name", name);
goto fail;
}
if (e->device_valid && !pa_namereg_is_valid_name(e->device)) {
pa_log_warn("Invalid device name stored in database for stream %s", name);
goto fail;
}
if (e->card_valid && !pa_namereg_is_valid_name(e->card)) {
pa_log_warn("Invalid card name stored in database for stream %s", name);
goto fail;
}
if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) {
pa_log_warn("Invalid channel map stored in database for stream %s", name);
goto fail;
@ -252,6 +263,10 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
(a->device_valid && strncmp(a->device, b->device, sizeof(a->device))))
return FALSE;
if (a->card_valid != b->card_valid ||
(a->card_valid && strncmp(a->card, b->card, sizeof(a->card))))
return FALSE;
if (a->muted_valid != b->muted_valid ||
(a->muted_valid && (a->muted != b->muted)))
return FALSE;
@ -308,6 +323,11 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
if (sink_input->save_sink) {
pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device));
entry.device_valid = TRUE;
if (sink_input->sink->card) {
pa_strlcpy(entry.card, sink_input->sink->card->name, sizeof(entry.card));
entry.card_valid = TRUE;
}
}
} else {
@ -327,6 +347,11 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
if (source_output->save_source) {
pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device));
entry.device_valid = source_output->save_source;
if (source_output->source->card) {
pa_strlcpy(entry.card, source_output->source->card->name, sizeof(entry.card));
entry.card_valid = TRUE;
}
}
}
@ -368,19 +393,28 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
if (!(name = get_name(new_data->proplist, "sink-input")))
return PA_HOOK_OK;
if ((e = read_entry(u, name))) {
if (new_data->sink)
pa_log_debug("Not restoring device for stream %s, because already set.", name);
else if ((e = read_entry(u, name))) {
pa_sink *s = NULL;
if (e->device_valid) {
pa_sink *s;
if (e->device_valid)
s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK);
if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK))) {
if (!new_data->sink) {
if (!s && e->card_valid) {
pa_card *card;
if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
s = pa_idxset_first(card->sinks, NULL);
}
/* It might happen that a stream and a sink are set up at the
same time, in which case we want to make sure we don't
interfere with that */
if (s && PA_SINK_IS_LINKED(pa_sink_get_state(s))) {
pa_log_info("Restoring device for stream %s.", name);
new_data->sink = s;
new_data->save_sink = TRUE;
} else
pa_log_debug("Not restoring device for stream %s, because already set.", name);
}
}
pa_xfree(e);
@ -455,18 +489,28 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
if (!(name = get_name(new_data->proplist, "source-output")))
return PA_HOOK_OK;
if ((e = read_entry(u, name))) {
pa_source *s;
if (new_data->source)
pa_log_debug("Not restoring device for stream %s, because already set", name);
else if ((e = read_entry(u, name))) {
pa_source *s = NULL;
if (e->device_valid) {
if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE))) {
if (!new_data->source) {
if (e->device_valid)
s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE);
if (!s && e->card_valid) {
pa_card *card;
if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
s = pa_idxset_first(card->sources, NULL);
}
/* It might happen that a stream and a sink are set up at the
same time, in which case we want to make sure we don't
interfere with that */
if (s && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) {
pa_log_info("Restoring device for stream %s.", name);
new_data->source = s;
new_data->save_source = TRUE;
} else
pa_log_debug("Not restoring device for stream %s, because already set", name);
}
}
pa_xfree(e);
@ -496,6 +540,17 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct
if (si->save_sink)
continue;
/* Skip this if it is already in the process of being moved
* anyway */
if (!si->sink)
continue;
/* It might happen that a stream and a sink are set up at the
same time, in which case we want to make sure we don't
interfere with that */
if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
continue;
if (!(name = get_name(si->proplist, "sink-input")))
continue;
@ -534,6 +589,16 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
if (so->direct_on_input)
continue;
/* Skip this if it is already in the process of being moved anyway */
if (!so->source)
continue;
/* It might happen that a stream and a sink are set up at the
same time, in which case we want to make sure we don't
interfere with that */
if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
continue;
if (!(name = get_name(so->proplist, "source-input")))
continue;
@ -567,6 +632,9 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
char *name;
struct entry *e;
if (!si->sink)
continue;
if (!(name = get_name(si->proplist, "sink-input")))
continue;
@ -575,7 +643,9 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
if (e->device_valid) {
pa_sink *d;
if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) && d != sink)
if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) &&
d != sink &&
PA_SINK_IS_LINKED(pa_sink_get_state(d)))
pa_sink_input_move_to(si, d, TRUE);
}
@ -605,6 +675,12 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
char *name;
struct entry *e;
if (so->direct_on_input)
continue;
if (!so->source)
continue;
if (!(name = get_name(so->proplist, "source-output")))
continue;
@ -613,7 +689,9 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
if (e->device_valid) {
pa_source *d;
if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) && d != source)
if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) &&
d != source &&
PA_SOURCE_IS_LINKED(pa_source_get_state(d)))
pa_source_output_move_to(so, d, TRUE);
}
@ -856,6 +934,10 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
data.data = &entry;
data.size = sizeof(entry);
pa_log_debug("Client %s changes entry %s.",
pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)),
name);
if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0)
if (apply_immediately)
apply_entry(u, name, &entry);

View file

@ -145,6 +145,9 @@ static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_
pa_assert(data);
pa_assert(u);
if (data->flags & PA_SINK_INPUT_START_CORKED)
return PA_HOOK_OK;
if ((d = pa_hashmap_get(u->device_infos, data->sink)))
resume(d);
@ -158,6 +161,9 @@ static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_outpu
pa_assert(data);
pa_assert(u);
if (data->flags & PA_SOURCE_OUTPUT_START_CORKED)
return PA_HOOK_OK;
if (data->source->monitor_of)
d = pa_hashmap_get(u->device_infos, data->source->monitor_of);
else
@ -226,11 +232,16 @@ static pa_hook_result_t sink_input_move_start_hook_cb(pa_core *c, pa_sink_input
static pa_hook_result_t sink_input_move_finish_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
struct device_info *d;
pa_sink_input_state_t state;
pa_assert(c);
pa_sink_input_assert_ref(s);
pa_assert(u);
state = pa_sink_input_get_state(s);
if (state != PA_SINK_INPUT_RUNNING && state != PA_SINK_INPUT_DRAINED)
return PA_HOOK_OK;
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
resume(d);
@ -265,6 +276,9 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_
pa_source_output_assert_ref(s);
pa_assert(u);
if (pa_source_output_get_state(s) != PA_SOURCE_OUTPUT_RUNNING)
return PA_HOOK_OK;
if (s->source->monitor_of)
d = pa_hashmap_get(u->device_infos, s->source->monitor_of);
else
@ -279,6 +293,7 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_
static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
struct device_info *d;
pa_sink_input_state_t state;
pa_assert(c);
pa_sink_input_assert_ref(s);
pa_assert(u);
@ -292,15 +307,11 @@ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_inp
}
static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
pa_source_output_state_t state;
pa_assert(c);
pa_source_output_assert_ref(s);
pa_assert(u);
state = pa_source_output_get_state(s);
if (state == PA_SOURCE_OUTPUT_RUNNING) {
if (pa_source_output_get_state(s) == PA_SOURCE_OUTPUT_RUNNING) {
struct device_info *d;
if (s->source->monitor_of)
@ -387,23 +398,18 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s
pa_sink *s = PA_SINK(o);
pa_sink_state_t state = pa_sink_get_state(s);
if (pa_sink_check_suspend(s) <= 0) {
if (pa_sink_check_suspend(s) <= 0)
if (PA_SINK_IS_OPENED(state))
restart(d);
}
} else if (pa_source_isinstance(o)) {
pa_source *s = PA_SOURCE(o);
pa_source_state_t state = pa_source_get_state(s);
if (pa_source_check_suspend(s) <= 0) {
if (pa_source_check_suspend(s) <= 0)
if (PA_SOURCE_IS_OPENED(state))
restart(d);
}
}
return PA_HOOK_OK;
}

View file

@ -55,6 +55,7 @@
#include <pulsecore/core-error.h>
#include <pulsecore/proplist-util.h>
#include <pulsecore/auth-cookie.h>
#include <pulsecore/mcalign.h>
#ifdef TUNNEL_SINK
#include "module-tunnel-sink-symdef.h"
@ -194,6 +195,7 @@ struct userdata {
#else
char *source_name;
pa_source *source;
pa_mcalign *mcalign;
#endif
pa_auth_cookie *auth_cookie;
@ -330,7 +332,7 @@ static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
static void command_stream_buffer_attr_changed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
struct userdata *u = userdata;
uint32_t channel, maxlength, tlength, fragsize, prebuf, minreq;
uint32_t channel, maxlength, tlength = 0, fragsize, prebuf, minreq;
pa_usec_t usec;
pa_assert(pd);
@ -614,14 +616,23 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
return 0;
}
case SOURCE_MESSAGE_POST:
case SOURCE_MESSAGE_POST: {
pa_memchunk c;
pa_mcalign_push(u->mcalign, chunk);
while (pa_mcalign_pop(u->mcalign, &c) >= 0) {
if (PA_SOURCE_IS_OPENED(u->source->thread_info.state))
pa_source_post(u->source, chunk);
pa_source_post(u->source, &c);
u->counter += (int64_t) chunk->length;
pa_memblock_unref(c.memblock);
u->counter += (int64_t) c.length;
}
return 0;
}
case SOURCE_MESSAGE_REMOTE_SUSPEND:
@ -1086,7 +1097,7 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag
uint32_t idx, owner_module, client, sink;
pa_usec_t buffer_usec, sink_usec;
const char *name, *driver, *resample_method;
pa_bool_t mute;
pa_bool_t mute = FALSE;
pa_sample_spec sample_spec;
pa_channel_map channel_map;
pa_cvolume volume;
@ -1151,13 +1162,13 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag
pa_assert(u->sink);
if ((u->version < 11 || !!mute == !!u->sink->muted) &&
pa_cvolume_equal(&volume, &u->sink->virtual_volume))
pa_cvolume_equal(&volume, &u->sink->real_volume))
return;
pa_sink_volume_changed(u->sink, &volume, FALSE);
pa_sink_volume_changed(u->sink, &volume);
if (u->version >= 11)
pa_sink_mute_changed(u->sink, mute, FALSE);
pa_sink_mute_changed(u->sink, mute);
return;
@ -1334,12 +1345,11 @@ static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32
/* Called from main context */
static void start_subscribe(struct userdata *u) {
pa_tagstruct *t;
uint32_t tag;
pa_assert(u);
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE);
pa_tagstruct_putu32(t, tag = u->ctag++);
pa_tagstruct_putu32(t, u->ctag++);
pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER|
#ifdef TUNNEL_SINK
PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK
@ -1515,7 +1525,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
reply = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME);
pa_tagstruct_putu32(reply, tag = u->ctag++);
pa_tagstruct_putu32(reply, u->ctag++);
if (u->version >= 13) {
pa_proplist *pl;
@ -1742,7 +1752,6 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
static void sink_set_volume(pa_sink *sink) {
struct userdata *u;
pa_tagstruct *t;
uint32_t tag;
pa_assert(sink);
u = sink->userdata;
@ -1750,9 +1759,9 @@ static void sink_set_volume(pa_sink *sink) {
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME);
pa_tagstruct_putu32(t, tag = u->ctag++);
pa_tagstruct_putu32(t, u->ctag++);
pa_tagstruct_putu32(t, u->device_index);
pa_tagstruct_put_cvolume(t, &sink->virtual_volume);
pa_tagstruct_put_cvolume(t, &sink->real_volume);
pa_pstream_send_tagstruct(u->pstream, t);
}
@ -1760,7 +1769,6 @@ static void sink_set_volume(pa_sink *sink) {
static void sink_set_mute(pa_sink *sink) {
struct userdata *u;
pa_tagstruct *t;
uint32_t tag;
pa_assert(sink);
u = sink->userdata;
@ -1771,7 +1779,7 @@ static void sink_set_mute(pa_sink *sink) {
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE);
pa_tagstruct_putu32(t, tag = u->ctag++);
pa_tagstruct_putu32(t, u->ctag++);
pa_tagstruct_putu32(t, u->device_index);
pa_tagstruct_put_boolean(t, !!sink->muted);
pa_pstream_send_tagstruct(u->pstream, t);
@ -1937,6 +1945,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);
u->mcalign = pa_mcalign_new(pa_frame_size(&u->source->sample_spec));
#endif
pa_xfree(dn);
@ -2030,6 +2040,11 @@ void pa__done(pa_module*m) {
if (u->time_event)
u->core->mainloop->time_free(u->time_event);
#ifndef TUNNEL_SINK
if (u->mcalign)
pa_mcalign_free(u->mcalign);
#endif
#ifdef TUNNEL_SINK
pa_xfree(u->sink_name);
#else

View file

@ -25,13 +25,17 @@
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <sys/inotify.h>
#include <libudev.h>
#include <pulse/timeval.h>
#include <pulsecore/modargs.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core-util.h>
#include <pulsecore/namereg.h>
#include <pulsecore/ratelimit.h>
#include "module-udev-detect-symdef.h"
@ -39,18 +43,25 @@ PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);
PA_MODULE_USAGE(
"tsched=<enable system timer based scheduling mode?> "
"ignore_dB=<ignore dB information from the device?>");
struct device {
char *path;
pa_bool_t accessible;
pa_bool_t need_verify;
char *card_name;
char *args;
uint32_t module;
pa_ratelimit ratelimit;
};
struct userdata {
pa_core *core;
pa_hashmap *devices;
pa_bool_t use_tsched;
pa_bool_t use_tsched:1;
pa_bool_t ignore_dB:1;
struct udev* udev;
struct udev_monitor *monitor;
@ -62,14 +73,18 @@ struct userdata {
static const char* const valid_modargs[] = {
"tsched",
"ignore_dB",
NULL
};
static int setup_inotify(struct userdata *u);
static void device_free(struct device *d) {
pa_assert(d);
pa_xfree(d->path);
pa_xfree(d->card_name);
pa_xfree(d->args);
pa_xfree(d);
}
@ -88,35 +103,204 @@ static const char *path_get_card_id(const char *path) {
return e + 5;
}
static pa_bool_t is_card_busy(const char *id) {
char *card_path = NULL, *pcm_path = NULL, *sub_status = NULL;
DIR *card_dir = NULL, *pcm_dir = NULL;
FILE *status_file = NULL;
size_t len;
struct dirent *space = NULL, *de;
pa_bool_t busy = FALSE;
int r;
pa_assert(id);
/* This simply uses /proc/asound/card.../pcm.../sub.../status to
* check whether there is still a process using this audio device. */
card_path = pa_sprintf_malloc("/proc/asound/card%s", id);
if (!(card_dir = opendir(card_path))) {
pa_log_warn("Failed to open %s: %s", card_path, pa_cstrerror(errno));
goto fail;
}
len = offsetof(struct dirent, d_name) + fpathconf(dirfd(card_dir), _PC_NAME_MAX) + 1;
space = pa_xmalloc(len);
for (;;) {
de = NULL;
if ((r = readdir_r(card_dir, space, &de)) != 0) {
pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r));
goto fail;
}
if (!de)
break;
if (!pa_startswith(de->d_name, "pcm"))
continue;
pa_xfree(pcm_path);
pcm_path = pa_sprintf_malloc("%s/%s", card_path, de->d_name);
if (pcm_dir)
closedir(pcm_dir);
if (!(pcm_dir = opendir(pcm_path))) {
pa_log_warn("Failed to open %s: %s", pcm_path, pa_cstrerror(errno));
continue;
}
for (;;) {
char line[32];
if ((r = readdir_r(pcm_dir, space, &de)) != 0) {
pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r));
goto fail;
}
if (!de)
break;
if (!pa_startswith(de->d_name, "sub"))
continue;
pa_xfree(sub_status);
sub_status = pa_sprintf_malloc("%s/%s/status", pcm_path, de->d_name);
if (status_file)
fclose(status_file);
if (!(status_file = fopen(sub_status, "r"))) {
pa_log_warn("Failed to open %s: %s", sub_status, pa_cstrerror(errno));
continue;
}
if (!(fgets(line, sizeof(line)-1, status_file))) {
pa_log_warn("Failed to read from %s: %s", sub_status, pa_cstrerror(errno));
continue;
}
if (!pa_streq(line, "closed\n")) {
busy = TRUE;
break;
}
}
}
fail:
pa_xfree(card_path);
pa_xfree(pcm_path);
pa_xfree(sub_status);
pa_xfree(space);
if (card_dir)
closedir(card_dir);
if (pcm_dir)
closedir(pcm_dir);
if (status_file)
fclose(status_file);
return busy;
}
static void verify_access(struct userdata *u, struct device *d) {
char *cd;
pa_card *card;
pa_bool_t accessible;
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));
accessible = access(cd, R_OK|W_OK) >= 0;
pa_log_debug("%s is accessible: %s", cd, pa_yes_no(accessible));
pa_xfree(cd);
pa_card_suspend(card, !d->accessible, PA_SUSPEND_SESSION);
if (d->module == PA_INVALID_INDEX) {
/* If we are not loaded, try to load */
if (accessible) {
pa_module *m;
pa_bool_t busy;
/* Check if any of the PCM devices that belong to this
* card are currently busy. If they are, don't try to load
* right now, to make sure the probing phase can
* successfully complete. When the current user of the
* device closes it we will get another notification via
* inotify and can then recheck. */
busy = is_card_busy(path_get_card_id(d->path));
pa_log_debug("%s is busy: %s", d->path, pa_yes_no(busy));
if (!busy) {
/* So, why do we rate limit here? It's certainly ugly,
* but there seems to be no other way. Problem is
* this: if we are unable to configure/probe an audio
* device after opening it we will close it again and
* the module initialization will fail. This will then
* cause an inotify event on the device node which
* will be forwarded to us. We then try to reopen the
* audio device again, practically entering a busy
* loop.
*
* A clean fix would be if we would be able to ignore
* our own inotify close events. However, inotify
* lacks such functionality. Also, during probing of
* the device we cannot really distuingish between
* other processes causing EBUSY or ourselves, which
* means we have no way to figure out if the probing
* during opening was canceled by a "try again"
* failure or a "fatal" failure. */
if (pa_ratelimit_test(&d->ratelimit)) {
pa_log_debug("Loading module-alsa-card with arguments '%s'", d->args);
m = pa_module_load(u->core, "module-alsa-card", d->args);
if (m) {
d->module = m->index;
pa_log_info("Card %s (%s) module loaded.", d->path, d->card_name);
} else
pa_log_info("Card %s (%s) failed to load module.", d->path, d->card_name);
} else
pa_log_warn("Tried to configure %s (%s) more often than %u times in %llus",
d->path,
d->card_name,
d->ratelimit.burst,
(long long unsigned) (d->ratelimit.interval / PA_USEC_PER_SEC));
}
}
} else {
/* If we are already loaded update suspend status with
* accessible boolean */
if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD)))
pa_card_suspend(card, !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);
/* Maybe /dev/snd is now available? */
setup_inotify(u);
path = udev_device_get_devpath(dev);
if ((d = pa_hashmap_get(u->devices, path))) {
@ -124,42 +308,34 @@ static void card_changed(struct userdata *u, struct udev_device *dev) {
return;
}
d = pa_xnew0(struct device, 1);
d->path = pa_xstrdup(path);
d->module = PA_INVALID_INDEX;
PA_INIT_RATELIMIT(d->ratelimit, 10*PA_USEC_PER_SEC, 5);
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\" "
d->card_name = pa_sprintf_malloc("alsa_card.%s", n);
d->args = pa_sprintf_malloc("device_id=\"%s\" "
"name=\"%s\" "
"card_name=\"%s\" "
"tsched=%i "
"tsched=%s "
"ignore_dB=%s "
"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;
d->card_name,
pa_yes_no(u->use_tsched),
pa_yes_no(u->ignore_dB));
pa_xfree(n);
pa_hashmap_put(u->devices, d->path, d);
} else
pa_xfree(card_name);
pa_xfree(n);
verify_access(u, d);
}
static void remove_card(struct userdata *u, struct udev_device *dev) {
@ -172,7 +348,10 @@ static void remove_card(struct userdata *u, struct udev_device *dev) {
return;
pa_log_info("Card %s removed.", d->path);
if (d->module != PA_INVALID_INDEX)
pa_module_unload_request_by_index(u->core, d->module, TRUE);
device_free(d);
}
@ -249,6 +428,34 @@ fail:
u->udev_io = NULL;
}
static pa_bool_t pcm_node_belongs_to_device(
struct device *d,
const char *node) {
char *cd;
pa_bool_t b;
cd = pa_sprintf_malloc("pcmC%sD", path_get_card_id(d->path));
b = pa_startswith(node, cd);
pa_xfree(cd);
return b;
}
static pa_bool_t control_node_belongs_to_device(
struct device *d,
const char *node) {
char *cd;
pa_bool_t b;
cd = pa_sprintf_malloc("controlC%s", path_get_card_id(d->path));
b = pa_streq(node, cd);
pa_xfree(cd);
return b;
}
static void inotify_cb(
pa_mainloop_api*a,
pa_io_event* e,
@ -262,10 +469,13 @@ static void inotify_cb(
} buf;
struct userdata *u = userdata;
static int type = 0;
pa_bool_t verify = FALSE;
pa_bool_t deleted = FALSE;
struct device *d;
void *state;
for (;;) {
ssize_t r;
struct inotify_event *event;
pa_zero(buf);
if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) {
@ -277,25 +487,60 @@ static void inotify_cb(
goto fail;
}
if ((buf.e.mask & IN_CLOSE_WRITE) && pa_startswith(buf.e.name, "pcmC"))
verify = TRUE;
event = &buf.e;
while (r > 0) {
size_t len;
if ((size_t) r < sizeof(struct inotify_event)) {
pa_log("read() too short.");
goto fail;
}
if (verify) {
struct device *d;
void *state;
len = sizeof(struct inotify_event) + event->len;
pa_log_debug("Verifying access.");
if ((size_t) r < len) {
pa_log("Payload missing.");
goto fail;
}
/* From udev we get the guarantee that the control
* device's ACL is changed last. To avoid races when ACLs
* are changed we hence watch only the control device */
if (((event->mask & IN_ATTRIB) && pa_startswith(event->name, "controlC")))
PA_HASHMAP_FOREACH(d, u->devices, state)
if (control_node_belongs_to_device(d, event->name))
d->need_verify = TRUE;
/* ALSA doesn't really give us any guarantee on the closing
* order, so let's simply hope */
if (((event->mask & IN_CLOSE_WRITE) && pa_startswith(event->name, "pcmC")))
PA_HASHMAP_FOREACH(d, u->devices, state)
if (pcm_node_belongs_to_device(d, event->name))
d->need_verify = TRUE;
/* /dev/snd/ might have been removed */
if ((event->mask & (IN_DELETE_SELF|IN_MOVE_SELF)))
deleted = TRUE;
event = (struct inotify_event*) ((uint8_t*) event + len);
r -= len;
}
}
PA_HASHMAP_FOREACH(d, u->devices, state)
if (d->need_verify) {
d->need_verify = FALSE;
verify_access(u, d);
}
if (!deleted)
return;
fail:
if (u->inotify_io) {
a->io_free(u->inotify_io);
u->inotify_io = NULL;
}
if (u->inotify_fd >= 0) {
pa_close(u->inotify_fd);
@ -307,17 +552,38 @@ static int setup_inotify(struct userdata *u) {
char *dev_snd;
int r;
if (u->inotify_fd >= 0)
return 0;
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);
r = inotify_add_watch(u->inotify_fd, dev_snd, IN_ATTRIB|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF);
pa_xfree(dev_snd);
if (r < 0) {
pa_log("inotify_add_watch() failed: %s", pa_cstrerror(errno));
int saved_errno = errno;
pa_close(u->inotify_fd);
u->inotify_fd = -1;
if (saved_errno == ENOENT) {
pa_log_debug("/dev/snd/ is apparently not existing yet, retrying to create inotify watch later.");
return 0;
}
if (saved_errno == ENOSPC) {
pa_log("You apparently ran out of inotify watches, probably because Tracker/Beagle took them all away. "
"I wished people would do their homework first and fix inotify before using it for watching whole "
"directory trees which is something the current inotify is certainly not useful for. "
"Please make sure to drop the Tracker/Beagle guys a line complaining about their broken use of inotify.");
return 0;
}
pa_log("inotify_add_watch() failed: %s", pa_cstrerror(saved_errno));
return -1;
}
@ -332,6 +598,7 @@ int pa__init(pa_module *m) {
struct udev_enumerate *enumerate = NULL;
struct udev_list_entry *item = NULL, *first = NULL;
int fd;
pa_bool_t use_tsched = TRUE, ignore_dB = FALSE;
pa_assert(m);
@ -343,13 +610,19 @@ int pa__init(pa_module *m) {
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.");
if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
pa_log("Failed to parse tsched= argument.");
goto fail;
}
u->use_tsched = use_tsched;
if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) {
pa_log("Failed to parse ignore_dB= argument.");
goto fail;
}
u->ignore_dB = ignore_dB;
if (!(u->udev = udev_new())) {
pa_log("Failed to initialize udev library.");
@ -402,7 +675,7 @@ int pa__init(pa_module *m) {
udev_enumerate_unref(enumerate);
pa_log_info("Loaded %u modules.", pa_hashmap_size(u->devices));
pa_log_info("Found %u cards.", pa_hashmap_size(u->devices));
pa_modargs_free(ma);

View file

@ -48,6 +48,7 @@ static const char* const valid_modargs[] = {
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
pa_bool_t restore_device = TRUE, restore_volume = TRUE;
pa_module *n;
char *t;
pa_assert(m);
@ -66,13 +67,15 @@ int pa__init(pa_module*m) {
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);
n = pa_module_load(m->core, "module-stream-restore", t);
pa_xfree(t);
if (n)
pa_module_unload_request(m, TRUE);
pa_modargs_free(ma);
return 0;
return n ? 0 : -1;
fail:
if (ma)

View file

@ -812,11 +812,11 @@ static void sink_get_volume(pa_sink *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
if (u->mixer_devmask & SOUND_MASK_VOLUME)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->real_volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_PCM)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->real_volume) >= 0)
return;
pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
@ -830,11 +830,11 @@ static void sink_set_volume(pa_sink *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
if (u->mixer_devmask & SOUND_MASK_VOLUME)
if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->real_volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_PCM)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->real_volume) >= 0)
return;
pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
@ -848,11 +848,11 @@ static void source_get_volume(pa_source *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
if (u->mixer_devmask & SOUND_MASK_IGAIN)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_RECLEV)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume) >= 0)
return;
pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
@ -866,11 +866,11 @@ static void source_set_volume(pa_source *s) {
pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
if (u->mixer_devmask & SOUND_MASK_IGAIN)
if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume) >= 0)
return;
if (u->mixer_devmask & SOUND_MASK_RECLEV)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume) >= 0)
return;
pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));

View file

@ -57,7 +57,6 @@ int pa_base64_encode(const void *data, int size, char **str)
p = s = pa_xnew(char, size * 4 / 3 + 4);
q = (const unsigned char *) data;
i = 0;
for (i = 0; i < size;) {
c = q[i++];
c *= 256;

View file

@ -283,15 +283,15 @@ static void sink_set_volume_cb(pa_sink *s) {
/* Calculate the max volume of all channels.
We'll use this as our (single) volume on the APEX device and emulate
any variation in channel volumes in software */
v = pa_cvolume_max(&s->virtual_volume);
v = pa_cvolume_max(&s->real_volume);
/* Create a pa_cvolume version of our single value */
pa_cvolume_set(&hw, s->sample_spec.channels, v);
/* Perform any software manipulation of the volume needed */
pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &hw);
pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw);
pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume));
pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &hw));
pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume));

View file

@ -38,6 +38,7 @@ struct rm_monitor {
char *device_name;
char *service_name;
char *match;
DBusConnection *connection;
@ -51,12 +52,18 @@ struct rm_monitor {
#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
#define SERVICE_FILTER \
"type='signal'," \
"sender='" DBUS_SERVICE_DBUS "'," \
"interface='" DBUS_INTERFACE_DBUS "'," \
"member='NameOwnerChanged'," \
"arg0='%s'"
static DBusHandlerResult filter_handler(
DBusConnection *c,
DBusMessage *s,
void *userdata) {
DBusMessage *reply;
rm_monitor *m;
DBusError error;
@ -97,31 +104,10 @@ static DBusHandlerResult filter_handler(
}
}
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;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
int rm_watch(
@ -175,11 +161,13 @@ int rm_watch(
m->filtering = 1;
dbus_bus_add_match(m->connection,
"type='signal',"
"sender='" DBUS_SERVICE_DBUS "',"
"interface='" DBUS_INTERFACE_DBUS "',"
"member='NameOwnerChanged'", error);
if (!(m->match = malloc(sizeof(SERVICE_FILTER) - 2 + strlen(m->service_name)))) {
r = -ENOMEM;
goto fail;
}
sprintf(m->match, SERVICE_FILTER, m->service_name);
dbus_bus_add_match(m->connection, m->match, error);
if (dbus_error_is_set(error)) {
r = -EIO;
@ -220,10 +208,8 @@ void rm_release(rm_monitor *m) {
if (m->matching)
dbus_bus_remove_match(
m->connection,
"type='signal',"
"sender='" DBUS_SERVICE_DBUS "',"
"interface='" DBUS_INTERFACE_DBUS "',"
"member='NameOwnerChanged'", NULL);
m->match,
NULL);
if (m->filtering)
dbus_connection_remove_filter(
@ -233,6 +219,7 @@ void rm_release(rm_monitor *m) {
free(m->device_name);
free(m->service_name);
free(m->match);
if (m->connection)
dbus_connection_unref(m->connection);

Some files were not shown because too many files have changed in this diff Show more