mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-14 06:59:53 -05:00
Merge branch 'master' of git://0pointer.de/pulseaudio
This commit is contained in:
commit
bebaa49165
231 changed files with 35120 additions and 22532 deletions
|
|
@ -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
|
||||
|
|
|
|||
112
configure.ac
112
configure.ac
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1414
po/bn_IN.po
1414
po/bn_IN.po
File diff suppressed because it is too large
Load diff
1232
po/de_CH.po
1232
po/de_CH.po
File diff suppressed because it is too large
Load diff
1791
po/pt_BR.po
1791
po/pt_BR.po
File diff suppressed because it is too large
Load diff
1433
po/sr@latin.po
1433
po/sr@latin.po
File diff suppressed because it is too large
Load diff
1210
po/zh_CN.po
1210
po/zh_CN.po
File diff suppressed because it is too large
Load diff
1463
pulseaudio.vapi
Normal file
1463
pulseaudio.vapi
Normal file
File diff suppressed because it is too large
Load diff
1
src/.gitignore
vendored
1
src/.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
usergroup-test
|
||||
sigbus-test
|
||||
TAGS
|
||||
alsa-time-test
|
||||
|
|
|
|||
110
src/Makefile.am
110
src/Makefile.am
|
|
@ -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)\" \
|
||||
|
|
@ -82,8 +84,8 @@ AM_CFLAGS = \
|
|||
-DAO_REQUIRE_CAS \
|
||||
-DPULSE_LOCALEDIR=\"$(pulselocaledir)\" \
|
||||
-DPA_MACHINE_ID=\"$(localstatedir)/lib/dbus/machine-id\" \
|
||||
-DPA_ALSA_PATHS_DIR=\"$(alsapathsdir)\" \
|
||||
-DPA_ALSA_PROFILE_SETS_DIR=\"$(alsaprofilesetsdir)\"
|
||||
-DPA_ALSA_PATHS_DIR=\"$(alsapathsdir)\" \
|
||||
-DPA_ALSA_PROFILE_SETS_DIR=\"$(alsaprofilesetsdir)\"
|
||||
|
||||
AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS)
|
||||
AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,9 +259,14 @@ static int change_user(void) {
|
|||
pa_set_env("HOME", PA_SYSTEM_RUNTIME_PATH);
|
||||
|
||||
/* Relevant for pa_runtime_path() */
|
||||
pa_set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH);
|
||||
pa_set_env("PULSE_CONFIG_PATH", PA_SYSTEM_CONFIG_PATH);
|
||||
pa_set_env("PULSE_STATE_PATH", PA_SYSTEM_STATE_PATH);
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
37
src/daemon/pulseaudio-system.conf
Normal file
37
src/daemon/pulseaudio-system.conf
Normal 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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -62,11 +62,26 @@
|
|||
/* #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_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 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_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;
|
||||
|
|
@ -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",
|
||||
(double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
|
||||
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",
|
||||
(double) new_min_latency / PA_USEC_PER_MSEC);
|
||||
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;
|
||||
s->real_volume = u->hardware_volume = r;
|
||||
|
||||
if (u->mixer_path->has_dB) {
|
||||
pa_cvolume reset;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
/* Hmm, so the hardware volume changed, let's reset our software volume */
|
||||
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));
|
||||
return -1;
|
||||
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,7 +1399,8 @@ static void thread_func(void *userdata) {
|
|||
* we have filled the buffer at least once
|
||||
* completely.*/
|
||||
|
||||
pa_log_debug("Cutting sleep time for the initial iterations by half.");
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -59,11 +59,24 @@
|
|||
/* #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_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 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;
|
||||
|
|
@ -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",
|
||||
(double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
|
||||
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",
|
||||
(double) new_min_latency / PA_USEC_PER_MSEC);
|
||||
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;
|
||||
s->volume = u->hardware_volume = r;
|
||||
|
||||
if (u->mixer_path->has_dB) {
|
||||
pa_cvolume reset;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
/* Hmm, so the hardware volume changed, let's reset our software volume */
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
_ss.channels = c;
|
||||
}
|
||||
|
||||
if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) {
|
||||
pa_log_debug("snd_pcm_hw_params_set_periods_integer() failed: %s", pa_alsa_strerror(ret));
|
||||
goto finish;
|
||||
if (_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 (_period_size && tsched_size && _periods) {
|
||||
if (_buffer_size > 0 || _period_size > 0) {
|
||||
snd_pcm_uframes_t max_frames = 0;
|
||||
|
||||
/* 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 ((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 %lu ms", (long unsigned) max_frames * PA_MSEC_PER_SEC / _ss.rate);
|
||||
|
||||
if (_use_tsched) {
|
||||
snd_pcm_uframes_t buffer_size = 0;
|
||||
/* 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 ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size)) < 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);
|
||||
if (_buffer_size > 0 && _period_size > 0) {
|
||||
snd_pcm_hw_params_copy(hwparams_copy, hwparams);
|
||||
|
||||
_period_size = tsched_size;
|
||||
_periods = 1;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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 (_period_size > 0 && _periods > 0) {
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
if (_buffer_size > 0) {
|
||||
snd_pcm_hw_params_copy(hwparams_copy, hwparams);
|
||||
|
||||
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));
|
||||
/* 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 (_periods > 0) {
|
||||
if (_period_size > 0) {
|
||||
snd_pcm_hw_params_copy(hwparams_copy, hwparams);
|
||||
|
||||
/* 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));
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0)
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
1
src/modules/alsa/mixer/Makefile
Symbolic link
1
src/modules/alsa/mixer/Makefile
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../pulse/Makefile
|
||||
1
src/modules/alsa/mixer/paths/Makefile
Symbolic link
1
src/modules/alsa/mixer/paths/Makefile
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../pulse/Makefile
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1
src/modules/alsa/mixer/profile-sets/Makefile
Symbolic link
1
src/modules/alsa/mixer/profile-sets/Makefile
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../pulse/Makefile
|
||||
|
|
@ -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__;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,18 +1462,21 @@ 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);
|
||||
|
||||
pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
|
||||
(unsigned long long) skip_usec,
|
||||
(unsigned long long) skip_bytes);
|
||||
if (skip_bytes > 0) {
|
||||
pa_memchunk tmp;
|
||||
|
||||
pa_sink_render_full(u->sink, skip_bytes, &tmp);
|
||||
pa_memblock_unref(tmp.memblock);
|
||||
u->write_index += skip_bytes;
|
||||
pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
|
||||
(unsigned long long) skip_usec,
|
||||
(unsigned long long) skip_bytes);
|
||||
|
||||
pa_sink_render_full(u->sink, skip_bytes, &tmp);
|
||||
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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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_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;
|
||||
}
|
||||
|
||||
pa_rtpoll_item_free(o->inq_rtpoll_item_read);
|
||||
o->inq_rtpoll_item_read = NULL;
|
||||
|
||||
pa_rtpoll_item_free(o->outq_rtpoll_item_write);
|
||||
o->outq_rtpoll_item_write = NULL;
|
||||
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,84 +891,135 @@ 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:
|
||||
|
||||
if (o) {
|
||||
pa_idxset_remove_by_data(u->outputs, o, NULL);
|
||||
|
||||
if (o->sink_input) {
|
||||
pa_sink_input_unlink(o->sink_input);
|
||||
pa_sink_input_unref(o->sink_input);
|
||||
}
|
||||
|
||||
if (o->memblockq)
|
||||
pa_memblockq_free(o->memblockq);
|
||||
|
||||
if (o->inq)
|
||||
pa_asyncmsgq_unref(o->inq);
|
||||
|
||||
if (o->outq)
|
||||
pa_asyncmsgq_unref(o->outq);
|
||||
|
||||
pa_xfree(o);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static void output_free(struct output *o) {
|
||||
pa_assert(o);
|
||||
|
||||
output_disable(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);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
84
src/modules/module-hal-detect-compat.c
Normal file
84
src/modules/module-hal-detect-compat.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,24 +303,28 @@ 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);
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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) =
|
||||
|
||||
/* 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);
|
||||
|
||||
*((pa_usec_t*) data) = usec;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
|
||||
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)))
|
||||
|
||||
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
|
||||
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]) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
784
src/modules/module-loopback.c
Normal file
784
src/modules/module-loopback.c
Normal 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 = ©
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
return PA_HOOK_OK;
|
||||
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);
|
||||
return PA_HOOK_OK;
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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) =
|
||||
/* 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);
|
||||
|
||||
*((pa_usec_t*) data) = usec;
|
||||
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)))
|
||||
|
||||
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,39 +675,32 @@ 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:
|
||||
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);
|
||||
break;
|
||||
default:
|
||||
pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} 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;
|
||||
} else {
|
||||
pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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,22 +398,17 @@ 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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
if (PA_SOURCE_IS_OPENED(u->source->thread_info.state))
|
||||
pa_source_post(u->source, chunk);
|
||||
pa_mcalign_push(u->mcalign, chunk);
|
||||
|
||||
u->counter += (int64_t) chunk->length;
|
||||
while (pa_mcalign_pop(u->mcalign, &c) >= 0) {
|
||||
|
||||
if (PA_SOURCE_IS_OPENED(u->source->thread_info.state))
|
||||
pa_source_post(u->source, &c);
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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\" "
|
||||
"name=\"%s\" "
|
||||
"card_name=\"%s\" "
|
||||
"tsched=%i "
|
||||
"card_properties=\"module-udev-detect.discovered=1\"",
|
||||
path_get_card_id(path),
|
||||
n,
|
||||
card_name,
|
||||
(int) u->use_tsched);
|
||||
|
||||
pa_log_debug("Loading module-alsa-card with arguments '%s'", args);
|
||||
m = pa_module_load(u->core, "module-alsa-card", args);
|
||||
pa_xfree(args);
|
||||
|
||||
if (m) {
|
||||
pa_log_info("Card %s (%s) added.", path, n);
|
||||
|
||||
d = pa_xnew(struct device, 1);
|
||||
d->path = pa_xstrdup(path);
|
||||
d->card_name = card_name;
|
||||
d->module = m->index;
|
||||
d->accessible = TRUE;
|
||||
|
||||
pa_hashmap_put(u->devices, d->path, d);
|
||||
} else
|
||||
pa_xfree(card_name);
|
||||
|
||||
d->card_name = pa_sprintf_malloc("alsa_card.%s", n);
|
||||
d->args = pa_sprintf_malloc("device_id=\"%s\" "
|
||||
"name=\"%s\" "
|
||||
"card_name=\"%s\" "
|
||||
"tsched=%s "
|
||||
"ignore_dB=%s "
|
||||
"card_properties=\"module-udev-detect.discovered=1\"",
|
||||
path_get_card_id(path),
|
||||
n,
|
||||
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);
|
||||
|
||||
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);
|
||||
pa_module_unload_request_by_index(u->core, d->module, TRUE);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
len = sizeof(struct inotify_event) + event->len;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (verify) {
|
||||
struct device *d;
|
||||
void *state;
|
||||
|
||||
pa_log_debug("Verifying access.");
|
||||
|
||||
PA_HASHMAP_FOREACH(d, u->devices, state)
|
||||
PA_HASHMAP_FOREACH(d, u->devices, state)
|
||||
if (d->need_verify) {
|
||||
d->need_verify = FALSE;
|
||||
verify_access(u, d);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
if (!deleted)
|
||||
return;
|
||||
|
||||
fail:
|
||||
a->io_free(u->inotify_io);
|
||||
u->inotify_io = NULL;
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
pa_module_unload_request(m, TRUE);
|
||||
if (n)
|
||||
pa_module_unload_request(m, TRUE);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
return 0;
|
||||
|
||||
return n ? 0 : -1;
|
||||
|
||||
fail:
|
||||
if (ma)
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue