Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 119 additions & 13 deletions module/service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,94 @@ start_daemon() {
# Log: /data/local/tmp/awl_pulse.log (root-owned fd, inherited by the child).
# Every skip path writes its reason there — a silent skip is what made "no
# sound" undiagnosable after a reflash.
PULSE_PROBE_TIMEOUT=3
PULSE_PROBE_POLLS=6
PULSE_START_RETRIES=3

stop_pulse() {
# The runtime copy is disposable, but the process must be stopped before it
# is replaced. Otherwise an old instance can keep the old UID/audio state
# alive across a module update or an APK reinstall.
if pkill -f "$PAR/bin/pulseaudio" 2>/dev/null; then
sleep 1
# A stuck instance must not survive into the next attempt and race the
# new server for the socket or AudioFlinger track.
pkill -KILL -f "$PAR/bin/pulseaudio" 2>/dev/null || true
fi
rm -f "$RT/pulse.sock"
}

pulse_query() {
# pactl in the staged tree is dynamically linked against the staged libpulse;
# use the same runtime environment as the server. timeout is provided by
# Android toybox and prevents a half-created socket from blocking boot.
PULSE_SERVER="unix:$RT/pulse.sock" \
HOME="$PH" TMPDIR="$PH" \
LD_LIBRARY_PATH="$PAR/lib:$PAR/lib/pulseaudio:$PAR/lib/pulseaudio/modules" \
timeout "$PULSE_PROBE_TIMEOUT" "$PAR/bin/pactl" "$@"
}

pulse_is_ready() {
[ -S "$RT/pulse.sock" ] || return 1
[ -x "$PAR/bin/pactl" ] || {
echo "anland: pulse probe unavailable ($PAR/bin/pactl missing)" >> "$LOG"
return 1
}

SINKS=$(pulse_query list short sinks 2>>"$LOG")
status=$?
if [ "$status" -ne 0 ]; then
echo "anland: pulse probe failed (pactl status $status)" >> "$LOG"
return 1
fi

{
echo "anland: pulse sinks:"
printf '%s\n' "$SINKS"
} >> "$LOG"

# A native socket and a successful pactl connection are insufficient: the
# daemon can otherwise fall back to module-always-sink/auto_null after
# OpenSL ES rejects the stream. SUSPENDED is valid here when idle.
printf '%s\n' "$SINKS" | awk \
'$2 == "android" && $3 == "module-sles-sink.c" { found = 1 }
END { exit(found ? 0 : 1) }'
}

wait_for_pulse() {
probe=1
while [ "$probe" -le "$PULSE_PROBE_POLLS" ]; do
if pulse_is_ready; then
return 0
fi
if [ "$probe" -lt "$PULSE_PROBE_POLLS" ]; then
sleep 1
fi
probe=$((probe + 1))
done
return 1
}

launch_pulse() {
attempt="$1"
rm -rf "$PH"
mkdir -p "$PH/run" "$PH/state" || {
echo "anland: pulse attempt $attempt failed (cannot create $PH)" >> "$LOG"
return 1
}
chown -R "$PAUID:$PAUID" "$PH"
chmod 700 "$PH" "$PH/run" "$PH/state"
rm -f "$RT/pulse.sock"
echo "anland: pulse starting as uid $PAUID (attempt $attempt/$PULSE_START_RETRIES; tree $PAR, socket $RT/pulse.sock)" >> "$LOG"
nohup su "$PAUID" -c "export HOME='$PH' TMPDIR='$PH' PULSE_RUNTIME_PATH='$PH/run' \
PULSE_STATE_PATH='$PH/state' PULSE_CONFIG_PATH='$PAR/etc/pulse' \
LD_LIBRARY_PATH='$PAR/lib:$PAR/lib/pulseaudio:$PAR/lib/pulseaudio/modules'; \
exec '$PAR/bin/pulseaudio' --daemonize=no --exit-idle-time=-1 --disallow-exit \
--log-target=stderr -n -F '$PAR/etc/pulse/default.pa' \
-L 'module-native-protocol-unix auth-anonymous=1 socket=$RT/pulse.sock'" \
>> "$LOG" 2>&1 &
}

start_pulse() {
PA="$MODDIR/pulse"
LOG=/data/local/tmp/awl_pulse.log
Expand All @@ -58,25 +146,43 @@ start_pulse() {
PAR="$RT/pulse"
# a previous instance (repair path, or an app reinstall that changed the
# uid) still holds the old tree and socket — replace it whole
pkill -f "$PAR/bin/pulseaudio" 2>/dev/null && sleep 1
PH="$RT/pulse-home"
stop_pulse
rm -rf "$PAR"
cp -r "$PA" "$PAR"
cp -r "$PA" "$PAR" || {
echo "anland: pulse failed (cannot copy $PA to $PAR)" > "$LOG"
return 1
}
chmod -R 755 "$PAR"
chcon u:object_r:awl_daemon_exec:s0 "$PAR/bin/pulseaudio" 2>/dev/null
# module lookup: daemon.conf is read from PULSE_CONFIG_PATH (this copy)
echo "dl-search-path = $PAR/lib/pulseaudio/modules" >> "$PAR/etc/pulse/daemon.conf"
PH="$RT/pulse-home"
rm -rf "$PH"; mkdir -p "$PH/run" "$PH/state"
chown -R "$PAUID:$PAUID" "$PH"; chmod 700 "$PH" "$PH/run" "$PH/state"

attempt=1
while [ "$attempt" -le "$PULSE_START_RETRIES" ]; do
stop_pulse
launch_pulse "$attempt"
if wait_for_pulse; then
echo "anland: pulse ready (android/module-sles-sink.c)" >> "$LOG"
return 0
fi

echo "anland: pulse attempt $attempt/$PULSE_START_RETRIES did not produce an Android sink" >> "$LOG"
stop_pulse
if [ "$attempt" -lt "$PULSE_START_RETRIES" ]; then
case "$attempt" in
1) delay=1 ;;
2) delay=2 ;;
*) delay=4 ;;
esac
sleep "$delay"
fi
attempt=$((attempt + 1))
done

rm -f "$RT/pulse.sock"
echo "anland: pulse starting as uid $PAUID (tree $PAR, socket $RT/pulse.sock)" > "$LOG"
nohup su "$PAUID" -c "export HOME='$PH' TMPDIR='$PH' PULSE_RUNTIME_PATH='$PH/run' \
PULSE_STATE_PATH='$PH/state' PULSE_CONFIG_PATH='$PAR/etc/pulse' \
LD_LIBRARY_PATH='$PAR/lib:$PAR/lib/pulseaudio:$PAR/lib/pulseaudio/modules'; \
exec '$PAR/bin/pulseaudio' --daemonize=no --exit-idle-time=-1 --disallow-exit \
--log-target=stderr -n -F '$PAR/etc/pulse/default.pa' \
-L 'module-native-protocol-unix auth-anonymous=1 socket=$RT/pulse.sock'" \
>> "$LOG" 2>&1 &
echo "anland: pulse failed after $PULSE_START_RETRIES attempts (no android/module-sles-sink.c)" >> "$LOG"
return 1
}

case "${1:-}" in
Expand Down
Loading