#!/bin/bash
version=0.0.10
tmpdir=$TMPDIR
if test -z "$tmpdir"; then
tmpdir="/tmp"
fi
tmpdir=$tmpdir/alsa-delay-script
vardir=$VARDIR
if test -z "$vardir"; then
vardir="/var/lib/misc"
fi
vardir=$vardir/alsa-delay-script
delay=
pcard=
pdev=
ccard=
cdev=
yes=
quiet=
clean=
revert=
pdevice="default:%s"
cdevice="plughw:%s,%s,%s"
pctl=
cctl=
one=
arg=
mix=
pa=
useprocfs=yes
fuser_prg=fuser
insmod_prg=insmod
rmmod_prg=rmmod
lsmod_prg=lsmod
modprobe_prg=modprobe
chkconfig_prg=chkconfig
pidof_prg=pidof
alsaloop_prg=alsaloop
amixer_prg=amixer
test -x /sbin/fuser && fuser_prg=/sbin/fuser
test -x /sbin/insmod && insmod_prg=/sbin/insmod
test -x /sbin/rmmod && rmmod_prg=/sbin/rmmod
test -x /sbin/lsmod && lsmod_prg=/sbin/lsmod
test -x /sbin/modprobe && modprobe_prg=/sbin/modprobe
test -x /sbin/chkconfig && chkconfig_prg=/sbin/chkconfig
test -x /sbin/pidof && pidof_prg=/sbin/pidof
test -x /usr/bin/alsaloop && alsaloop_prg=/usr/bin/alsaloop
test -x /usr/local/bin/alsaloop && alsaloop_prg=/usr/local/bin/alsaloop
test -x /usr/bin/amixer && amixer_prg=/usr/bin/amixer
modprobeconf=/etc/modprobe.d/alsa.conf
test -r /etc/modprobe.conf && modprobeconf=/etc/modprobe.conf && useprocfs=""
alsaloopconf=/etc/alsaloop.conf
test -r modprobe.work && modprobeconf=modprobe.work
test -r modprobe.work && alsaloopconf=alsaloop.conf
usage() {
echo "Usage: $0 [OPTION]... [[,]]"
cat < is ALSA card index (number) or string card identifier
is ALSA device number
Use 'aplay -l' to list available cards and devices.
Operation modes:
-h, --help print this help, then exit
-q, --quiet quiet mode
-y, --yes do not ask any questions - answer is always yes
-c, --clean clean temporary directory
-r, --revert revert configuration changes (modprobe, pulse-audio)
--tmpdir= set temporary directory
Alsaloop options:
--pdevice= force playback device
--cdevice= force capture device
--pctl= force playback ctl device
--cctl= force capture ctl device
--one= pass this argument to last thread
--arg= pass this argument to all threads
--mix= redirect ALSA mixer controls to OSS mixer
(default is Master)
--pa Redirect pulse-audio to alsaloop
Note: For devices, the string %s is replaced with the card index
and second string %s is replaced with the device index
and third string %s is replaced the substream index (0-7).
Example: hw:%s,%s,%s (card, device, substream)
EOF
}
while :
do
case "$1" in
-h|--help)
usage
exit 0
;;
-q|--quiet)
quiet=true ;;
-y|--yes)
yes=true ;;
-c|--clean)
clean="full" ;;
-r|--revert)
revert=true ;;
--tmpdir*)
case "$#,$1" in
*,*=*)
tmpdir=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
tmpdir="$2"
shift ;;
esac
tmpdir="$tmpdir/alsa-compile-script"
;;
--pdevice*)
case "$#,$1" in
*,*=*)
pdevice=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
pdevice="$2"
shift ;;
esac
;;
--cdevice*)
case "$#,$1" in
*,*=*)
cdevice=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
cdevice="$2"
shift ;;
esac
;;
--pctl*)
case "$#,$1" in
*,*=*)
pctl=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
pctl="$2"
shift ;;
esac
;;
--cctl*)
case "$#,$1" in
*,*=*)
cctl=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
cctl="$2"
shift ;;
esac
;;
--one*)
case "$#,$1" in
*,*=*)
one1=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
one1="$2"
shift ;;
esac
one="$one $one1"
;;
--arg*)
case "$#,$1" in
*,*=*)
arg1=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
arg1="$2"
shift ;;
esac
arg="$arg $arg1"
;;
--mix*)
case "$#,$1" in
*,*=*)
mix=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
mix="$2"
shift ;;
esac
arg="$arg $arg1"
;;
--pa)
pa=true
;;
*)
if test -n "$1"; then
ok=
if test -z "$delay"; then
delay="$1"
if test "$delay" -lt 1000; then
delay=$[$delay * 1000]
fi
ok=true
fi
if test -z "$ok" -a -z "$pcard"; then
pcard="$1"
ok=true
fi
if test -z "$ok"; then
echo "Unknown parameter '$1'"
break
fi
else
break
fi
;;
esac
shift
done
test -z "$delay" && delay="50000"
test -z "$pcard" && pcard="1"
pdev=$(echo $pcard | cut -s -d , -f 2)
pcard=$(echo $pcard | cut -d , -f 1)
test -z "$pdev" && pdev="0"
test -z "$ccard" && ccard="Loopback,1"
cdev=$(echo $ccard | cut -s -d , -f 2)
ccard=$(echo $ccard | cut -d , -f 1)
test -z "$cdev" && cdev="0"
# Echo "true" or "false", depending on $yes and user response to prompt
# $1 is prompt message
question_bool() {
if test "$yes" = "yes"; then
echo "true"
else
echo >&2 -n "$1 (Y/ ) "
read i
local i=${i:0:1}
if test "$i" = "Y" -o "$i" = "y"; then
echo "true"
else
echo "false"
fi
fi
}
# Log and execute $@ and check success
do_cmd() {
if test -z "$quiet"; then
echo "> $@"
fi
$@ || exit 1
}
# Cache or restore $protocol and $url and $package in $tmpdir
check_environment() {
if ! test -d $tmpdir ; then
mkdir -p $tmpdir
if ! test -d $tmpdir; then
echo >&2 "Unable to create directory $tmpdir."
exit 1
fi
fi
if ! test -d $vardir ; then
mkdir -p $vardir
if ! test -d $vardir; then
echo >&2 "Unable to create directory $vardir."
exit 1
fi
fi
echo "Using temporary tree: $tmpdir"
echo "Using backup tree : $vardir"
test -x /bin/depmod && depmodbin=/bin/depmod
test -x /sbin/depmod && depmodbin=/sbin/depmod
if test -z "$depmodbin"; then
echo >&2 "Unable to find depmod utility."
exit 1
fi
test -x /bin/modinfo && modinfobin=/bin/modinfo
test -x /sbin/modinfo && modinfobin=/sbin/modinfo
if test -z "$modinfobin"; then
echo >&2 "Unable to find modinfo utility."
exit 1
fi
}
# Kill processes currently accessing the audio devices
kill_audio_apps() {
local pids0=$($fuser_prg /dev/snd/* 2> /dev/null)
local pids1=$($fuser_prg /dev/mixer* 2> /dev/null)
local pids2=$($fuser_prg /dev/sequencer* 2> /dev/null)
local pids=
for pid in $pids0 $pids1 $pids2; do
local pids="$pids $pid"
done
if ! test -z "$pids"; then
echo
echo "WARNING! An audio application uses ALSA driver:"
echo
for pid in $pids; do
ps --no-headers -p $pids || exit 1
done
echo
if test $(question_bool "Would you like to kill these apps?") = "true"; then
for pid in $pids; do
do_cmd kill $pid
done
sleep 2
local killed=
for pid in $pids; do
local a=$(ps --no-headers -p $pids)
if test -n "$a"; then
do_cmd kill -9 $pid
local killed="true"
fi
done
if test "$killed" = "true"; then
sleep 2
for pid in $pids; do
local a=$(ps --no-headers -p $pids)
if test -n "$a"; then
echo >&2 "Unable to kill application:"
echo >&2 " $a"
exit 1
fi
done
fi
else
echo >&2 "Cannot continue with running audio applications."
exit 1
fi
fi
}
# Echo the list of configured sound modules
configured_modules() {
if test -z "$useprocfs"; then
cat $modprobeconf | grep -E "^alias snd-card-" | cut -d ' ' -f 3
else
cat /proc/asound/modules | colrm 1 3
fi
}
# Echo the list of loaded sound modules
current_modules() {
$lsmod_prg | cut -d ' ' -f 1 | grep -E "^(snd[_-])"
}
# Remove kernel modules, using two phases
# $@ is module names
my_rmmod() {
local phase2=
while test -n "$1"; do
if ! $rmmod_prg $1 2> /dev/null > /dev/null; then
local phase2="$phase2 $1"
else
echo "> rmmod $1"
fi
shift
done
for mod in $phase2; do
echo "> rmmod $mod"
if ! $rmmod_prg $mod ; then
echo >&2 "Unable to remove kernel module $mod."
exit 1
fi
done
}
# Reload kernel modules
kernel_modules() {
local curmods=$(current_modules)
local usermods=$(configured_modules)
kill_audio_apps
my_rmmod $curmods
$modprobe_prg soundcore || exit 1
for mod in $usermods; do
if ! $modprobe_prg $mod; then
echo >&2 "Unable to install kernel module $mod."
exit 1
fi
done
echo "Kernel modules ready:"
sleep 4
cat /proc/asound/cards
}
# If $package is alsa-driver then remove current modules
kernel_modules_remove() {
local curmods=$(current_modules)
if test -z "$curmods"; then
echo "No ALSA kernel modules to remove."
exit 0
fi
kill_audio_apps
my_rmmod $curmods
echo "ALSA kernel modules removed."
}
function clean() {
echo -n "Removing tree $tmpdir:"
if test -d "$tmpdir"; then
if ! rm -rf "$tmpdir"; then
echo " failed"
exit 1
fi
fi
echo " success"
}
function revert() {
echo "Running: $chkconfig_prg alsaloop off"
$chkconfig_prg alsaloop off
rm -v /etc/init.d/alsaloop
if test -f "$vardir/modprobe.conf"; then
echo -n "Copying original $modprobeconf from $vardir:"
cp "$vardir/modprobe.conf" $modprobeconf || exit 1
echo " success"
fi
if test -f "$vardir/pulse-default.pa"; then
echo -n "Copying original /etc/pulse/default.pa from $vardir:"
cp "$vardir/pulse-default.pa" /etc/pulse/default.pa || exit 1
echo " success"
fi
echo "Revert done. Reboot the system."
}
function reindex_modprobe_conf() {
if test -z "$useprocfs"; then
cat > $tmpdir/run.awk < 1)
printf(",")
printf("%i", c)
}
continue
}
printf(" %s", l[i])
}
printf("\n")
} else if (l[1] == "remove") {
printf(l[1])
flag = 0
for (i = 2; i <= length(l); i++) {
if (l[i] == "/usr/sbin/alsactl" || l[i] == "store") {
flag++;
printf(" %s", l[i]);
continue;
}
if (flag == 2) {
c = int(l[i]) + 1;
printf(" %i", c);
flag = 0;
continue;
}
flag = 0;
printf(" %s", l[i]);
}
printf("\n")
} else {
print line
}
}
BEGIN { aloop=0; }
/alias snd-card-0 snd-aloop/ { aloop=1; next; }
/options snd-card-0 index=0/ { if (aloop) next; }
/options snd-aloop/ { next; }
/alias snd-card-/ { rewrite_line(\$0); next; }
/options snd-/ { rewrite_line(\$0); next; }
/remove snd-/ { rewrite_line(\$0); next; }
{ print \$0; }
EOF
cat $modprobeconf | awk -f $tmpdir/run.awk > $modprobeconf.new
rm $tmpdir/run.awk || exit 1
else
local index=1
echo -n "" > $modprobeconf.new
declare -A modules printed
for mod in $(cat /proc/asound/modules | grep -Ev "^2[6789]" | colrm 1 3); do
if test "$mod" != "snd_aloop" -a "$mod" != "snd-aloop"; then
if test -z "${modules[$mod]}"; then
modules[$mod]="$index"
else
modules[$mod]="${modules[$mod]},$index"
fi
echo "alias snd-card-$index $mod" >> $modprobeconf.new
echo "options snd-card-$index index=$index" >> $modprobeconf.new
fi
index=$(expr $index + 1)
done
for mod in $(cat /proc/asound/modules | grep -Ev "^2[6789]" | colrm 1 3); do
if test "$mod" != "snd_aloop" -a "$mod" != "snd-aloop"; then
if test -z "${printed[$mod]}"; then
echo "options $mod index=${modules[$mod]}" >> $modprobeconf.new
printed[$mod]=yes
fi
fi
done
fi
cat >> $modprobeconf.new < /dev/null | grep -E "^alias snd-card-0 snd-aloop")
if test -n "$check"; then
echo "Module snd-aloop is already installed."
else
reindex_modprobe_conf
kernel_modules
fi
}
function myprintf() {
local cnt=$(echo "$1" | grep -o "%" | wc -l)
if test $cnt -eq 1; then
printf "$1" "$2"
else if test $cnt -eq 2; then
printf "$1" "$2" "$3"
else
printf "$1" "$2" "$3" "$4"
fi
fi
}
function check_ctl_name() {
$amixer_prg -D "$1" contents | grep "name='$2'"
}
function check_oss_mixer() {
grep ": mixer" /proc/asound/oss/devices
}
function get_card_id() {
$amixer_prg -D "$1" info | head -1 | cut -d '/' -f 1 | cut -d ' ' -f 3- | awk '{ print substr($0, 2, length($0)-2) }'
}
function generate_alsaloop_line() {
local file="$1"
local idx="$2"
local carg=$(myprintf "$cdevice" $ccard $cdev $idx)
local parg=$(myprintf "$pdevice" $pcard $pdev $idx)
local res="-C $carg -P $parg -T $idx -t $delay"
if test -n "$cctl"; then
local carg=$(myprintf "$cctl" $pcard $cdev $idx)
local res="$res -Y $carg"
fi
if test -n "$pctl"; then
local parg=$(myprintf "$pctl" $pcard $pdev $idx)
local res="$res -X $parg"
fi
if test -n "$arg"; then
local res="$res $arg"
fi
if test $idx -eq 7; then
if test -n "$one"; then
local res="$res $one"
fi
if test -z "$pctl"; then
local res="$res -X hw:$pcard"
fi
local mymix="$mix"
if test -z "$mymix"; then
local mymix="Master"
for mymix in Master "Master Mono" Headphone Headphone2 PCM Speaker \
"Desktop Speaker" Beep Front Rear Center LFE Side \
Surround ; do
local check=$(check_ctl_name hw:$pcard "$mymix Playback Switch")
if test -n "$check"; then
local res="$res -m \"name='$mymix Playback Switch'\""
fi
local check=$(check_ctl_name hw:$pcard "$mymix Playback Volume")
if test -n "$check"; then
local res="$res -m \"name='$mymix Playback Volume'\""
fi
done
else
local check=$(check_ctl_name hw:$pcard "$mymix Playback Switch")
if test -n "$check"; then
local res="$res -m \"name='$mymix Playback Switch'\""
fi
local check=$(check_ctl_name hw:$pcard "$mymix Playback Volume")
if test -n "$check"; then
local res="$res -m \"name='$mymix Playback Volume'\""
fi
fi
local check=$(check_oss_mixer)
if test -n "$check"; then
local res="$res -O \"$mymix@VOLUME\""
fi
fi
echo $res >> $file
}
function generate_alsaloop_conf() {
local idx=0
rm -f $alsaloopconf.new
while test $idx -lt 8; do
generate_alsaloop_line $alsaloopconf.new $idx
idx=$(expr $idx + 1)
done
mv $alsaloopconf.new $alsaloopconf || exit 1
}
function kill_alsaloop() {
pid=$($pidof_prg alsaloop)
if test -n "$pid"; then
echo "Killing alsaloop..."
fi
while test -n "$pid"; do
kill $pid
sleep 0.01
pid=$($pidof_prg alsaloop)
done
}
function restart_alsaloop() {
echo "Restarting alsaloop: delay $delay us."
/etc/init.d/alsaloop start
sleep 0.4
local pid=$($pidof_prg alsaloop)
if test -z "$pid"; then
echo "ERROR: Not started, check /var/log/messages for details"
else
ps -p $pid
fi
}
function create_initd() {
if test -x /etc/init.d/alsaloop; then
echo "/etc/init.d/alsaloop exists"
else
cat >> /etc/init.d/alsaloop <
# For RedHat and cousins:
# chkconfig: 2345 99 01
# description: Start alsaloop daemon
# processname: alsaloop
### BEGIN INIT INFO
# Provides: alsaloop
# Required-Start: \$local_fs
# Required-Stop: \$local_fs
# Should-Start:
# Short-Description: ALSALOOP
# Description: ALSALOOP
### END INIT INFO
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2, or (at your option) any later
# version.
# You should have received a copy of the GNU General Public License (for
# example COPYING); if not, write to the Free Software Foundation, Inc., 675
# Mass Ave, Cambridge, MA 02139, USA.
# This code was originally developed as a Senior Thesis by Michael Cornwell
# at the Concurrent Systems Laboratory (now part of the Storage Systems
# Research Center), Jack Baskin School of Engineering, University of
# California, Santa Cruz. http://ssrc.soe.ucsc.edu/.
alsaloop_opts="--daemonize --workaround serialopen --config $alsaloopconf"
ALSALOOP_BIN=/usr/bin/alsaloop
test -x /usr/local/bin/alsaloop && ALSALOOP_BIN=/usr/local/bin/alsaloop
# Source function library
. /etc/rc.d/init.d/functions
RETVAL=0
prog=alsaloop
pidfile=/var/lock/subsys/alsaloop
start()
{
modprobe snd-aloop
echo -n \$"Starting \$prog: "
daemon \$ALSALOOP_BIN \$alsaloop_opts
RETVAL=\$?
echo
[ \$RETVAL = 0 ] && touch \$pidfile
return \$RETVAL
}
stop()
{
echo -n \$"Shutting down \$prog: "
killproc \$ALSALOOP_BIN
RETVAL=\$?
echo
rm -f \$pidfile
return \$RETVAL
}
case "\$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
try-restart)
if [ -f \$pidfile ]; then
stop
start
fi
;;
status)
status $prog
RETVAL=$?
;;
*)
echo \$"Usage: \$0 {start|stop|restart|try-restart}"
RETVAL=3
esac
exit \$RETVAL
EOF
chmod 755 /etc/init.d/alsaloop || exit 1
$chkconfig_prg --add alsaloop
$chkconfig_prg alsaloop on
fi
}
function modify_pa_conf() {
if grep Loopback /etc/pulse/default.pa > /dev/null; then
echo "/etc/pulse/default.pa already modified"
else
if test -f /etc/pulse/default.pa; then
local pcardid=$(get_card_id "hw:$pcard")
cat /etc/pulse/default.pa | \
sed -e 's/^load-module module-udev-detect/#load-module module-udev-detect # commented by alsa-delay/' \
-e 's/^load-module module-detect/#load-module module-detect # commented by alsa-delay/' \
-e "s/^### Automatically load driver modules for Bluetooth hardware/load-module module-alsa-card device_id=Loopback\nload-module module-alsa-card device_id=$pcardid\n\n### Automatically load driver modules for Bluetooth hardware/" > /etc/pulse/default.pa.new
if test -s /etc/pulse/default.pa.new; then
cp /etc/pulse/default.pa "$vardir/pulse-default.pa"
mv /etc/pulse/default.pa.new /etc/pulse/default.pa
echo "/etc/pulse/default.pa changed"
echo " *** use --revert option to revert configuration changes ***"
fi
fi
fi
local file=
local rh=
if test -d /etc/alsa -a -f /etc/alsa/pulse-default.conf; then
file=/etc/alsa/alsaloop-default.conf
rh=yes
fi
if test -z "$file"; then
echo "unknown PulseAudio setup (asound.conf)"
elif test -f $file; then
echo "$file exists"
else
cat >> $file < /etc/asound.conf.new
if test -s /etc/asound.conf.new; then
mv /etc/asound.conf.new /etc/asound.conf
fi
fi
fi
}
rundir=$(pwd)
export LC_ALL=C
export LANGUAGE=C
check_environment
if test -n "$clean"; then
clean
fi
if test -n "$revert"; then
revert
exit 0
fi
kill_alsaloop
create_initd
modify_modprobe_conf
generate_alsaloop_conf
if test -n "$pa"; then
modify_pa_conf
fi
restart_alsaloop
clean
exit 0