S2: RECORD_AUDIO für Termux selbst, Quelle explizit, Diagnose dreistufig

Aus dem ersten Gerätelauf: module-sles-source lädt, die Aufnahme meldet einen
kräftigen Pegel (Spitze 21658) — und trotzdem ist nichts zu hören. Drei Fehler
kamen dabei zusammen.

1. provision.sh vergab RECORD_AUDIO nur an com.termux.api. PulseAudio läuft aber
   IM Termux-Prozess und öffnet das Mikrofon von dort über OpenSL ES. Die
   Berechtigung muss com.termux haben; sonst lädt das Modul zwar, liefert aber
   nichts Brauchbares. S2 prüft den Status jetzt vorab und nennt beide Wege, ihn
   zu setzen.

2. parecord lief ohne -d und nahm damit die Standardquelle. Bei geladenem
   Sink-Modul ist das häufig dessen Monitor — der zeichnet auf, was abgespielt
   wird, nicht was am Mikrofon anliegt. Eine solche Aufnahme sieht technisch
   einwandfrei aus und enthält doch nie eine Stimme. Jetzt wird OpenSL_ES_source
   ausdrücklich gewählt.

3. 'Man hört nichts' hat drei mögliche Ursachen, die der Test nicht getrennt hat.
   Er läuft jetzt dreistufig: erst ein Testton (kommt überhaupt Ton heraus?),
   dann die Aufnahme, dann automatisches Gegenhören. Ohne Stufe 1 sagt ein
   stummes Gegenhören nichts aus.

Die Auswertung misst zusätzlich die Schwankung des Pegels. Sprache hat laute
Silben und Pausen dazwischen; ein gleichmäßiges Signal ist Brummen oder ein
Monitor-Mitschnitt. Gegen Stille, Brummen und sprachähnliches Signal getestet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan
2026-08-09 20:23:21 +02:00
co-authored by Claude Opus 5
parent eeab61a0a4
commit f08399ba1d
2 changed files with 160 additions and 74 deletions
+15 -1
View File
@@ -214,6 +214,19 @@ done
# ── Berechtigungen ───────────────────────────────────────────────────────────
kopf "Berechtigungen"
# Termux selbst braucht RECORD_AUDIO und Speicherzugriff — nicht nur Termux:API.
# PulseAudio läuft IM Termux-Prozess und öffnet das Mikrofon über OpenSL ES von
# dort aus. Eine Berechtigung, die nur Termux:API hat, nützt ihm nichts: das
# Modul lädt dann zwar, liefert aber keinen brauchbaren Ton.
for perm in \
android.permission.RECORD_AUDIO \
android.permission.READ_EXTERNAL_STORAGE \
android.permission.WRITE_EXTERNAL_STORAGE
do
adbs "${perm##*.} für Termux" pm grant "$TERMUX" "$perm" 2>/dev/null || true
done
# Termux:API bedient Kamera, Sensoren, Standort und Taschenlampe.
for perm in \
android.permission.RECORD_AUDIO \
android.permission.CAMERA \
@@ -221,9 +234,10 @@ for perm in \
android.permission.READ_EXTERNAL_STORAGE \
android.permission.WRITE_EXTERNAL_STORAGE
do
adbs "$(basename "${perm//./\/}") für Termux:API" pm grant com.termux.api "$perm" 2>/dev/null || true
adbs "${perm##*.} für Termux:API" pm grant com.termux.api "$perm" 2>/dev/null || true
done
hinweis "Fehlschläge sind normal, solange Termux:API noch nicht installiert ist."
hinweis "Eine App, die eine Berechtigung gar nicht anmeldet, lässt sich nicht beschenken."
# ── Was ADB nicht kann ───────────────────────────────────────────────────────
kopf "Von Hand zu erledigen"
+145 -73
View File
@@ -3,8 +3,13 @@
#
# Der stillste K.-o.-Punkt des Projekts. Ohne Mikrofon im Linux gibt es keine
# Videocalls in Element oder im Browser, keine Sprachaufnahme, keine Spracheingabe.
# Die Wette: Termux' PulseAudio kann über OpenSL ES eine Aufnahmequelle anbieten,
# die per TCP in den proot durchgereicht wird.
#
# Der Test ist bewusst dreistufig, weil "man hört nichts" drei völlig
# verschiedene Ursachen haben kann und man sie sonst nicht auseinanderhält:
#
# Stufe 1 Wiedergabe — kommt überhaupt Ton aus dem Linux heraus?
# Stufe 2 Aufnahme — liefert die Mikrofonquelle Daten?
# Stufe 3 Gegenhören — ist auf der Aufnahme wirklich Sprache?
#
# Ausführen IN TERMUX: bash spike/02-s2-mikrofon.sh
set -uo pipefail
@@ -13,44 +18,58 @@ require_termux
DISTRO=debian
DAUER="${HPOS_DAUER:-6}"
WAV="$OUT_DIR/s2-aufnahme.wav"
WAV_NAME="s2-aufnahme.wav"
WAV="$OUT_DIR/$WAV_NAME"
log "HPOS Phase 0 · S2 — Mikrofon im Linux"
log "Protokoll: $LOG"
# ── Berechtigung ─────────────────────────────────────────────────────────────
# Ohne erteilte Mikrofonberechtigung liefert die OpenSL-Quelle Stille statt eines
# Fehlers — der verwirrendste denkbare Fehlschlag. Deshalb vorher erzwingen.
section "Mikrofonberechtigung"
# Der häufigste Grund für eine formal korrekte, aber stumme Aufnahme: PulseAudio
# läuft IM Termux-Prozess und öffnet das Mikrofon über OpenSL ES von dort aus.
# Eine Berechtigung, die nur Termux:API hat, nützt ihm deshalb nichts.
section "Mikrofonberechtigung von Termux"
BERECHTIGT="unbekannt"
if RECHTE=$(dumpsys package com.termux 2>/dev/null | grep -i 'RECORD_AUDIO'); then
log "$(printf '%s' "$RECHTE" | sed 's/^/ /')"
case "$RECHTE" in
*granted=true*) BERECHTIGT=ja; ok "com.termux darf aufnehmen" ;;
*granted=false*) BERECHTIGT=nein; fail "com.termux darf NICHT aufnehmen" ;;
esac
else
warn "Konnte den Berechtigungsstatus nicht auslesen"
fi
if [ "$BERECHTIGT" = nein ]; then
log ""
fail "Das ist mit hoher Wahrscheinlichkeit die Ursache."
hint "Auf dem Telefon: Einstellungen → Apps → Termux → Berechtigungen → Mikrofon"
hint "Oder vom PC: adb shell pm grant com.termux android.permission.RECORD_AUDIO"
hint "Danach dieses Skript erneut ausführen."
log ""
printf ' Trotzdem weitermachen? [j/N] > '
read -r WEITER </dev/tty || WEITER=n
case "${WEITER:-n}" in j|J) : ;; *) exit 1 ;; esac
fi
# Zusätzlich Termux:API antesten — das löst nebenbei den Systemdialog aus.
if have termux-microphone-record; then
log " Kurzer Test über Termux:API, um den Berechtigungsdialog auszulösen …"
try termux-microphone-record -d -l 2 -f "$OUT_DIR/s2-vortest.m4a"
sleep 3
try termux-microphone-record -q
if [ -s "$OUT_DIR/s2-vortest.m4a" ]; then
ok "Termux:API kann aufnehmen — Berechtigung liegt vor"
else
fail "Termux:API konnte nicht aufnehmen"
hint "Einstellungen → Apps → Termux:API → Berechtigungen → Mikrofon erlauben."
hint "Ohne das ist S2 nicht aussagekräftig."
fi
else
warn "termux-microphone-record fehlt (Paket termux-api). Weiter ohne Vortest."
[ -s "$OUT_DIR/s2-vortest.m4a" ] && ok "Termux:API kann aufnehmen" \
|| warn "Termux:API konnte nicht aufnehmen"
fi
# ── PulseAudio mit Aufnahmequelle ────────────────────────────────────────────
# ── PulseAudio ───────────────────────────────────────────────────────────────
section "PulseAudio mit OpenSL-Quelle"
pulseaudio --kill 2>/dev/null || true
sleep 1
# module-sles-source ist der Teil, der wackelt: er existiert nicht in jedem
# Termux-Build. Deshalb erst mit, dann ohne — und der Unterschied wird protokolliert.
MIT_QUELLE=0
if pulseaudio --start --exit-idle-time=-1 \
--load="module-sles-source" \
--load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" \
>>"$LOG" 2>&1; then
MIT_QUELLE=1
ok "PulseAudio mit module-sles-source gestartet"
else
warn "module-sles-source ließ sich nicht laden — starte ohne"
@@ -59,66 +78,92 @@ else
fi
sleep 2
section "Verfügbare Quellen"
section "Quellen und Standardgeräte"
if have pactl; then
pactl list sources short 2>&1 | tee -a "$LOG" | sed 's/^/ /'
QUELLEN=$(pactl list sources short 2>/dev/null | wc -l)
if [ "$QUELLEN" -gt 0 ]; then
ok "$QUELLEN Aufnahmequelle(n) gemeldet"
else
fail "Keine einzige Aufnahmequelle — S2 wird so nicht gelingen"
hint "Falls module-sles-source fehlt, ist dieser Termux-Build ohne OpenSL-Aufnahme gebaut."
fi
else
warn "pactl fehlt, kann Quellen nicht auflisten"
pactl info 2>&1 | grep -Ei 'Default (Source|Sink)' | tee -a "$LOG" | sed 's/^/ /'
log " Quellen:"
pactl list sources short 2>&1 | tee -a "$LOG" | sed 's/^/ /'
# Die Standardquelle ist bei zwei Einträgen gern der Sink-Monitor. Der nimmt
# auf, was ABGESPIELT wird, nicht was am Mikrofon anliegt — eine Aufnahme
# daraus sieht technisch einwandfrei aus und enthält doch nie eine Stimme.
STANDARD=$(pactl info 2>/dev/null | awk -F': ' '/Default Source/{print $2}')
case "$STANDARD" in
*monitor*) warn "Standardquelle ist ein Monitor ($STANDARD) — deshalb wird gleich"
hint "ausdrücklich OpenSL_ES_source gewählt statt der Vorgabe." ;;
esac
fi
# ── Aufnahme im Debian ───────────────────────────────────────────────────────
section "Aufnahme aus dem Debian heraus"
log " ${DAUER} Sekunden werden aufgenommen — bitte jetzt laut und deutlich sprechen."
log ""
# ── Stufe 1: Wiedergabe ──────────────────────────────────────────────────────
section "Stufe 1 — kommt Ton heraus?"
log " Gleich sollte ein kurzer Ton zu hören sein. Lautstärke aufdrehen."
sleep 1
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- bash -lc '
export PULSE_SERVER=127.0.0.1
command -v sox >/dev/null 2>&1 || apt-get install -y --no-install-recommends sox >/dev/null 2>&1
if command -v sox >/dev/null 2>&1; then
sox -n -r 44100 -c 1 /tmp/ton.wav synth 1.5 sine 440 vol 0.4 2>/dev/null
paplay /tmp/ton.wav && echo " Ton abgespielt."
else
echo " ! sox nicht verfügbar, Wiedergabetest übersprungen"
fi
' 2>&1 | tee -a "$LOG"
printf '\n War der Ton zu hören? [j/N] > '
read -r TON_GEHOERT </dev/tty || TON_GEHOERT=n
case "${TON_GEHOERT:-n}" in
j|J) ok "Wiedergabe funktioniert — ein späteres Schweigen liegt dann an der Aufnahme." ;;
*) warn "Keine Wiedergabe. Dann sagt der Gegenhör-Test unten nichts aus:"
hint "eine gute Aufnahme wäre trotzdem nicht zu hören." ;;
esac
# ── Stufe 2: Aufnahme ────────────────────────────────────────────────────────
section "Stufe 2 — Aufnahme"
log " ${DAUER} Sekunden — bitte jetzt laut und deutlich sprechen."
sleep 1
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- bash -lc "
HPOS_DAUER="$DAUER" HPOS_WAV="/hpos/spike/out/$WAV_NAME" \
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- bash -lc '
set -uo pipefail
export PULSE_SERVER=127.0.0.1
echo ' Quellen aus Sicht des Debian:'
pactl list sources short 2>&1 | sed 's/^/ /' || echo ' (pactl lieferte nichts)'
echo ' Nehme auf …'
timeout $((DAUER + 4)) parecord --channels=1 --rate=16000 --file-format=wav \
/hpos/spike/out/$(basename "$WAV") &
REC=\$!
sleep $DAUER
kill \$REC 2>/dev/null
wait \$REC 2>/dev/null
echo ' Aufnahme beendet.'
" 2>&1 | tee -a "$LOG"
# Ausdrücklich die Mikrofonquelle wählen. Ohne -d nimmt parecord die
# Standardquelle, und das ist bei geladenem Sink-Modul häufig dessen Monitor.
QUELLE=$(pactl list sources short 2>/dev/null | awk "/OpenSL_ES_source/ {print \$2; exit}")
if [ -z "$QUELLE" ]; then
QUELLE=$(pactl list sources short 2>/dev/null | awk "!/monitor/ {print \$2; exit}")
fi
if [ -n "$QUELLE" ]; then
echo " Aufnahmequelle: $QUELLE"
ARG="-d $QUELLE"
else
echo " ! Keine Nicht-Monitor-Quelle gefunden — nehme die Vorgabe."
ARG=""
fi
timeout $((HPOS_DAUER + 4)) parecord $ARG --channels=1 --rate=16000 \
--file-format=wav "$HPOS_WAV" &
REC=$!
sleep "$HPOS_DAUER"
kill $REC 2>/dev/null
wait $REC 2>/dev/null
echo " Aufnahme beendet."
' 2>&1 | tee -a "$LOG"
# ── Auswertung ───────────────────────────────────────────────────────────────
# Eine Datei zu erzeugen beweist nichts: die häufigste Fehlerform ist eine
# formal korrekte WAV-Datei voller Stille. Deshalb Größe UND Inhalt prüfen.
section "Auswertung"
if [ ! -s "$WAV" ]; then
fail "Keine Aufnahmedatei entstanden → S2 gescheitert"
else
GROESSE=$(stat -c%s "$WAV" 2>/dev/null || echo 0)
ok "Datei vorhanden: $WAV ($GROESSE Bytes)"
ERWARTET=$((DAUER * 16000 * 2 * 8 / 10))
if [ "$GROESSE" -lt "$ERWARTET" ]; then
warn "Deutlich kleiner als erwartet (~$ERWARTET Bytes) — vermutlich abgebrochen"
fi
# Stilletest. Der typische Fehlschlag ist eine formal korrekte WAV-Datei voller
# Nullen — sie sieht in jeder Dateiliste richtig aus und enthält doch nichts.
# Deshalb wird der Pegel gemessen, nicht nur die Größe.
ok "Datei: $WAV ($(stat -c%s "$WAV" 2>/dev/null || echo 0) Bytes)"
if have python; then
python - "$WAV" <<'PY' 2>&1 | tee -a "$LOG"
import array, math, sys, wave
# Bewusst ohne audioop: das Modul ist in Python 3.13 entfernt worden, und Termux
# liegt regelmäßig auf der aktuellen Version.
# Bewusst ohne audioop: das Modul ist in Python 3.13 entfernt worden.
try:
with wave.open(sys.argv[1]) as w:
breite = w.getsampwidth()
rate = w.getframerate()
roh = w.readframes(w.getnframes())
if breite == 1:
werte = array.array("b", bytes(b - 128 for b in roh))
@@ -128,27 +173,54 @@ try:
raise ValueError(f"{breite} Byte je Abtastwert werden hier nicht ausgewertet")
if not werte:
raise ValueError("keine Abtastwerte enthalten")
pegel = int(math.sqrt(sum(v * v for v in werte) / len(werte)))
spitze = max(abs(v) for v in werte)
print(f" Effektivpegel: {pegel} Spitze: {spitze}")
if spitze < 40:
print(" ✗ Praktisch Stille — die Quelle liefert keine echten Daten.")
print(" Das ist der typische Fehlschlag: Datei ja, Ton nein.")
print(" Prüfen: Mikrofonberechtigung für Termux:API, und ob")
print(" module-sles-source in diesem Termux-Build überhaupt existiert.")
# Sprache schwankt stark: laute Silben, Pausen dazwischen. Ein konstanter
# Pegel deutet auf Brummen, Rauschen oder ein Störsignal hin — technisch ein
# Signal, aber keine Aufnahme. Deshalb wird die Schwankung mitgemessen.
fenster = max(1, rate // 20)
bloecke = [werte[i:i + fenster] for i in range(0, len(werte), fenster)]
pegel_je_block = [
int(math.sqrt(sum(v * v for v in b) / len(b))) for b in bloecke if b
]
if len(pegel_je_block) > 4:
leise = min(pegel_je_block)
laut = max(pegel_je_block)
print(f" Leiseste Stelle: {leise} Lauteste: {laut}")
if spitze < 40:
print(" ✗ Praktisch Stille — die Quelle liefert keine Daten.")
elif laut > 4 * max(leise, 1):
print(" ✓ Deutliche Schwankung — sieht nach Sprache aus.")
else:
print(" ! Signal ohne Schwankung — eher Rauschen oder Brummen als Sprache.")
print(" Möglicherweise wurde ein Monitor statt des Mikrofons aufgenommen.")
elif spitze < 40:
print(" ✗ Praktisch Stille — die Quelle liefert keine Daten.")
else:
print(" ✓ Echtes Signal aufgenommen.")
print(" ✓ Signal vorhanden.")
except Exception as e:
print(f" ! Datei nicht auswertbar: {e}")
PY
fi
log ""
log " Zum Gegenhören: pactl upload-sample nicht nötig — einfach abspielen:"
log " proot-distro login $DISTRO -- env PULSE_SERVER=127.0.0.1 paplay /hpos/spike/out/$(basename "$WAV")"
# ── Stufe 3: Gegenhören ─────────────────────────────────────────────────
# Der einzige Test, der wirklich zählt. Kein Messwert ersetzt das Ohr.
section "Stufe 3 — Gegenhören"
log " Die Aufnahme wird jetzt abgespielt."
sleep 1
HPOS_WAV="/hpos/spike/out/$WAV_NAME" \
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- bash -lc '
export PULSE_SERVER=127.0.0.1
paplay "$HPOS_WAV" && echo " Wiedergabe beendet."
' 2>&1 | tee -a "$LOG"
fi
log ""
log " module-sles-source geladen: $([ "$MIT_QUELLE" = 1 ] && echo ja || echo NEIN)"
log " Berechtigung com.termux: $BERECHTIGT"
log " Ton vorher gehört: ${TON_GEHOERT:-n}"
verdict "S2" "Ist auf der Aufnahme deine Stimme hörbar?
Abspielen mit dem Befehl oben. Eine Datei ohne Ton zählt als gescheitert."
verdict "S2" "War auf der Wiedergabe deine Stimme zu hören?
Nur das zählt. Ein Messwert ohne hörbare Sprache ist ein Fehlschlag."