127 lines
4.2 KiB
Python
127 lines
4.2 KiB
Python
|
|
#!/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
|
||
|
|
<index> numerischer sounddevice-Index
|
||
|
|
<substring> 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()
|