fix(voice_loop): PipeWire-Aufnahme (pw-record) als Standard via --recorder auto
- arecord scheiterte auf PipeWire-Systemen ("Fehler beim Öffnen des Gerätes")
- neue Option --recorder {auto,pw-record,parecord,arecord}; auto bevorzugt pw-record
- geraetespezifische Flags je Werkzeug (-D / --target / --device)
- Doku: Recorder-Hinweis + Troubleshooting-Eintrag fuer den arecord-Fehler
- verifiziert: auto -> pw-record; --file-Round-Trip weiterhin ok
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7896f608fb
commit
d15e51eb0d
2 changed files with 56 additions and 11 deletions
|
|
@ -106,12 +106,18 @@ Ablauf je Runde:
|
||||||
|
|
||||||
Nützliche Optionen:
|
Nützliche Optionen:
|
||||||
```bash
|
```bash
|
||||||
python scripts/voice_loop.py --device hw:1,0 # bestimmtes Mikrofon (siehe: arecord -L)
|
python scripts/voice_loop.py --recorder pw-record # PipeWire-Aufnahme (Standard bei 'auto')
|
||||||
|
python scripts/voice_loop.py --recorder arecord --device hw:1,0 # ALSA, bestimmtes Mikrofon
|
||||||
python scripts/voice_loop.py --llm-provider openrouter --tts-provider openrouter
|
python scripts/voice_loop.py --llm-provider openrouter --tts-provider openrouter
|
||||||
python scripts/voice_loop.py --token "$TOKEN" # falls AUTH_ENABLED=true
|
python scripts/voice_loop.py --token "$TOKEN" # falls AUTH_ENABLED=true
|
||||||
python scripts/voice_loop.py --file frage.wav # ohne Mikrofon: WAV senden (Test)
|
python scripts/voice_loop.py --file frage.wav # ohne Mikrofon: WAV senden (Test)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Aufnahmewerkzeug:** `--recorder auto` (Standard) bevorzugt **PipeWire** (`pw-record`),
|
||||||
|
> sonst `parecord`/`arecord`. Gibt `arecord` den Fehler *„Fehler beim Öffnen des
|
||||||
|
> Gerätes"* aus, nutze `--recorder pw-record` (auf PipeWire-Systemen der Normalfall)
|
||||||
|
> oder gib mit `--device` ein konkretes Gerät an.
|
||||||
|
|
||||||
## A2. Nur tippen → Antwort hören
|
## A2. Nur tippen → Antwort hören
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -371,7 +377,8 @@ curl -s "$URL/api/metrics?format=prometheus" # Prometheus-Text (kein jq)
|
||||||
| HTTP **502** bei STT/TTS | Cloud-Fehler/Format | `make smoke` ausführen; Modellnamen in `.env` prüfen |
|
| HTTP **502** bei STT/TTS | Cloud-Fehler/Format | `make smoke` ausführen; Modellnamen in `.env` prüfen |
|
||||||
| `VA_PROFILE` wirkt nicht | `DEFAULT_*_PROVIDER` in `.env` überschreibt | diese Zeilen in `.env` auskommentieren |
|
| `VA_PROFILE` wirkt nicht | `DEFAULT_*_PROVIDER` in `.env` überschreibt | diese Zeilen in `.env` auskommentieren |
|
||||||
| `Address already in use` | Port belegt | anderen `PORT` setzen |
|
| `Address already in use` | Port belegt | anderen `PORT` setzen |
|
||||||
| Keine Aufnahme/Wiedergabe | `arecord`/Player/Gerät | `arecord -L` / `aplay -L`; Paket `alsa-utils`, `ffmpeg` |
|
| `arecord: Fehler beim Öffnen des Gerätes` | ALSA-`default` auf PipeWire nicht nutzbar | `--recorder pw-record` (Standard bei `auto`) oder `--device` mit Gerät aus `arecord -L` |
|
||||||
|
| Keine Aufnahme/Wiedergabe | Werkzeug/Gerät fehlt | `arecord -L` / `aplay -L`; Pakete `pipewire`/`alsa-utils`/`ffmpeg`; Default via `wpctl status` |
|
||||||
| Profil greift nicht | `config/voice-assistant.toml` fehlt | aus `*.example.toml` kopieren (Abschnitt 2) |
|
| Profil greift nicht | `config/voice-assistant.toml` fehlt | aus `*.example.toml` kopieren (Abschnitt 2) |
|
||||||
|
|
||||||
Logs erscheinen im Terminal von `make run`; mehr Details mit `LOG_LEVEL=debug` in `.env`.
|
Logs erscheinen im Terminal von `make run`; mehr Details mit `LOG_LEVEL=debug` in `.env`.
|
||||||
|
|
|
||||||
|
|
@ -54,21 +54,52 @@ def _first_player() -> list[str] | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def record_utterance(device: str | None, rate: int) -> bytes:
|
def resolve_recorder(choice: str) -> str:
|
||||||
|
"""Waehlt das Aufnahmewerkzeug. 'auto' bevorzugt PipeWire (pw-record)."""
|
||||||
|
if choice != "auto":
|
||||||
|
if not shutil.which(choice):
|
||||||
|
sys.exit(f"Fehlt: Aufnahmewerkzeug '{choice}' nicht gefunden.")
|
||||||
|
return choice
|
||||||
|
for tool in ("pw-record", "parecord", "arecord"):
|
||||||
|
if shutil.which(tool):
|
||||||
|
return tool
|
||||||
|
sys.exit("Kein Aufnahmewerkzeug gefunden (pw-record / parecord / arecord).")
|
||||||
|
|
||||||
|
|
||||||
|
def _record_cmd(recorder: str, device: str | None, rate: int, outfile: str) -> list[str]:
|
||||||
|
if recorder == "arecord":
|
||||||
|
cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(rate), "-c", "1"]
|
||||||
|
if device:
|
||||||
|
cmd += ["-D", device]
|
||||||
|
return cmd + [outfile]
|
||||||
|
if recorder == "pw-record":
|
||||||
|
cmd = ["pw-record", "--rate", str(rate), "--channels", "1", "--format", "s16"]
|
||||||
|
if device:
|
||||||
|
cmd += ["--target", device]
|
||||||
|
return cmd + [outfile]
|
||||||
|
if recorder == "parecord":
|
||||||
|
cmd = ["parecord", f"--rate={rate}", "--channels=1", "--format=s16le",
|
||||||
|
"--file-format=wav"]
|
||||||
|
if device:
|
||||||
|
cmd += [f"--device={device}"]
|
||||||
|
return cmd + [outfile]
|
||||||
|
sys.exit(f"Unbekanntes Aufnahmewerkzeug: {recorder}")
|
||||||
|
|
||||||
|
|
||||||
|
def record_utterance(recorder: str, device: str | None, rate: int) -> bytes:
|
||||||
"""Push-to-Talk: Enter startet, Enter stoppt die Aufnahme; gibt WAV-Bytes zurueck."""
|
"""Push-to-Talk: Enter startet, Enter stoppt die Aufnahme; gibt WAV-Bytes zurueck."""
|
||||||
_require("arecord")
|
|
||||||
tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
|
tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
|
||||||
tmp.close()
|
tmp.close()
|
||||||
cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(rate), "-c", "1"]
|
cmd = _record_cmd(recorder, device, rate, tmp.name)
|
||||||
if device:
|
|
||||||
cmd += ["-D", device]
|
|
||||||
cmd.append(tmp.name)
|
|
||||||
|
|
||||||
input("\n[Enter] = Aufnahme START …")
|
input("\n[Enter] = Aufnahme START …")
|
||||||
proc = subprocess.Popen(cmd)
|
proc = subprocess.Popen(cmd)
|
||||||
input("[Enter] = Aufnahme STOP …")
|
input("[Enter] = Aufnahme STOP …")
|
||||||
proc.send_signal(signal.SIGINT)
|
proc.send_signal(signal.SIGINT)
|
||||||
proc.wait(timeout=5)
|
try:
|
||||||
|
proc.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
proc.kill()
|
||||||
with open(tmp.name, "rb") as fh:
|
with open(tmp.name, "rb") as fh:
|
||||||
return fh.read()
|
return fh.read()
|
||||||
|
|
||||||
|
|
@ -142,10 +173,13 @@ async def run(args) -> None:
|
||||||
print(f"Sende Datei: {args.file}")
|
print(f"Sende Datei: {args.file}")
|
||||||
await one_turn(ws, wav, start_frame)
|
await one_turn(ws, wav, start_frame)
|
||||||
return
|
return
|
||||||
|
recorder = resolve_recorder(args.recorder)
|
||||||
|
print(f"Aufnahme mit: {recorder}"
|
||||||
|
+ (f" (Gerät: {args.device})" if args.device else " (Standardgerät)"))
|
||||||
print("Sprich nach 'START', stoppe mit Enter. Strg+C beendet den Loop.")
|
print("Sprich nach 'START', stoppe mit Enter. Strg+C beendet den Loop.")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
wav = record_utterance(args.device, args.rate)
|
wav = record_utterance(recorder, args.device, args.rate)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nEnde.")
|
print("\nEnde.")
|
||||||
return
|
return
|
||||||
|
|
@ -160,7 +194,11 @@ def main() -> None:
|
||||||
p.add_argument("--url", default="ws://127.0.0.1:8003/ws/voice")
|
p.add_argument("--url", default="ws://127.0.0.1:8003/ws/voice")
|
||||||
p.add_argument("--session", default="voice-loop")
|
p.add_argument("--session", default="voice-loop")
|
||||||
p.add_argument("--token", default=None, help="Bearer-Token, falls AUTH_ENABLED=true")
|
p.add_argument("--token", default=None, help="Bearer-Token, falls AUTH_ENABLED=true")
|
||||||
p.add_argument("--device", default=None, help="Aufnahmegeraet (siehe: arecord -L)")
|
p.add_argument("--device", default=None,
|
||||||
|
help="Aufnahmegeraet (arecord: -L; pw-record: --target; parecord: --device)")
|
||||||
|
p.add_argument("--recorder", default="auto",
|
||||||
|
choices=["auto", "pw-record", "parecord", "arecord"],
|
||||||
|
help="Aufnahmewerkzeug; 'auto' bevorzugt PipeWire (pw-record)")
|
||||||
p.add_argument("--rate", type=int, default=16000)
|
p.add_argument("--rate", type=int, default=16000)
|
||||||
p.add_argument("--stt-provider", dest="stt_provider", default=None)
|
p.add_argument("--stt-provider", dest="stt_provider", default=None)
|
||||||
p.add_argument("--llm-provider", dest="llm_provider", default=None)
|
p.add_argument("--llm-provider", dest="llm_provider", default=None)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue