#!/usr/bin/env python3 """ Mikrofon-Auswahl für sounddevice-Anwendungen. Kurzname-Aliase: respeaker ReSpeaker XVF3800, 48 kHz (default) motu MOTU M2 Mikrofon-Eingang, 44100 Hz camera USB-Kamera-Mikrofon (VF0680), 44100 Hz default System-Standard-Eingabe (PipeWire) bluetooth erstes verbundenes Bluetooth-Eingabegerät numerischer sounddevice-Index freie Substring-Suche im Gerätenamen Verwendung als Bibliothek: from mic import resolve_mic, print_mic_list device_idx, capture_rate, label = resolve_mic("motu") Verwendung als Skript: python3 mic.py # zeigt alle Eingabegeräte """ import sys import sounddevice as sd # Substring-Muster für bekannte Geräte (case-insensitive Suche) MIC_ALIASES: dict[str, str] = { "respeaker": "reSpeaker", # hw-Gerät kann in=0 melden, trotzdem nutzbar "motu": "M2:", "camera": "VF0680", } DEFAULT_MIC = "respeaker" def resolve_mic(spec: str) -> tuple[int, int, str]: """ Löst einen Mic-Bezeichner auf. Gibt (device_idx, native_samplerate, label) zurück. Reihenfolge: numerischer Index → "default" → "bluetooth" → Alias → Substring. Alias-Suche filtert NICHT nach in-Kanälen (ReSpeaker hw meldet in=0, läuft dennoch). """ devs = sd.query_devices() # Numerischer Index if spec.lstrip("-").isdigit(): idx = int(spec) d = devs[idx] return idx, int(d["default_samplerate"]), d["name"] # System-Standard-Eingabe if spec == "default": idx = sd.default.device[0] if idx < 0: raise ValueError("Kein System-Standard-Eingabegerät gefunden.") d = devs[idx] return idx, int(d["default_samplerate"]), f"{d['name']} (default)" # Erstes Bluetooth-Eingabegerät if spec == "bluetooth": for i, d in enumerate(devs): if "bluez" in d["name"].lower() and d["max_input_channels"] > 0: return i, int(d["default_samplerate"]), d["name"] raise ValueError("Kein Bluetooth-Eingabegerät gefunden.") # Alias → Substring-Suche ohne channels-Filter pattern = MIC_ALIASES.get(spec, spec) for i, d in enumerate(devs): if pattern.lower() in d["name"].lower(): return i, int(d["default_samplerate"]), d["name"] raise ValueError( f"Mikrofon '{spec}' nicht gefunden.\n" f"Verfügbare Geräte anzeigen: python3 mic.py" ) def list_mics() -> list[dict]: """Gibt alle Eingabegeräte mit Metadaten zurück (in-Kanäle > 0).""" return [ { "index": i, "name": d["name"], "channels": d["max_input_channels"], "rate": int(d["default_samplerate"]), } for i, d in enumerate(sd.query_devices()) if d["max_input_channels"] > 0 ] def print_mic_list() -> None: """Gibt eine formatierte Liste aller Eingabegeräte auf stdout aus.""" devs = list_mics() default_in = sd.default.device[0] print("Verfügbare Mikrofone:\n") print(f" {'Kurzname':<12} {'Idx':>3} {'Rate':>6} {'Ch':>3} Gerätename") print(f" {'-'*12} {'---':>3} {'------':>6} {'---':>3} {'-'*52}") shown: set[int] = set() for alias, pattern in MIC_ALIASES.items(): try: idx, rate, name = resolve_mic(alias) except ValueError: continue marker = " *" if idx == default_in else "" print(f" {alias:<12} {idx:>3} {rate:>6} {'?':>3} {name}{marker}") shown.add(idx) print(f" {'default':<12} {'':>3} {'':>6} {'':>3} System-Standard-Eingabe (PipeWire)") print(f" {'bluetooth':<12} {'':>3} {'':>6} {'':>3} erstes verbundenes BT-Eingabegerät (dynamisch)") others = [d for d in devs if d["index"] not in shown] if others: print("\n Weitere (mit Index oder Namens-Substring ansprechen):") for d in others: marker = " *" if d["index"] == default_in else "" print( f" {'':12} {d['index']:>3} {d['rate']:>6}" f" {d['channels']:>3} {d['name']}{marker}" ) print("\n * = System-Standard") if __name__ == "__main__": print_mic_list()