Add configurable input device selection

This commit is contained in:
2026-03-01 12:49:39 +03:00
parent a87840c78d
commit 7ca6958488
6 changed files with 50 additions and 2 deletions

View File

@@ -30,6 +30,8 @@ ANTHROPIC_API_VERSION=2023-06-01
DEEPGRAM_API_KEY=your_deepgram_api_key_here
PORCUPINE_ACCESS_KEY=your_porcupine_access_key_here
PORCUPINE_SENSITIVITY=0.8
AUDIO_INPUT_DEVICE_INDEX=
AUDIO_INPUT_DEVICE_NAME=
TTS_EN_SPEAKER=en_0
WEATHER_LAT=63.56
WEATHER_LON=53.69

View File

@@ -142,6 +142,8 @@ python run.py
| `DEEPGRAM_API_KEY` | Да | - | Ключ Deepgram STT |
| `PORCUPINE_ACCESS_KEY` | Да | - | Ключ PicoVoice Porcupine |
| `PORCUPINE_SENSITIVITY` | Нет | `0.8` | Чувствительность wake word |
| `AUDIO_INPUT_DEVICE_INDEX` | Нет | - | Явный индекс входного устройства для wake word и STT |
| `AUDIO_INPUT_DEVICE_NAME` | Нет | - | Часть имени входного устройства, например `pulse` или `pipewire` |
| `TTS_EN_SPEAKER` | Нет | `en_0` | Английский голос TTS |
| `WEATHER_LAT` | Нет | - | Широта города по умолчанию |
| `WEATHER_LON` | Нет | - | Долгота города по умолчанию |
@@ -206,8 +208,8 @@ alexander_smart-speaker/
| Проблема | Что проверить |
|---|---|
| Не реагирует на `Alexandr` | `PORCUPINE_ACCESS_KEY`, микрофон, чувствительность `PORCUPINE_SENSITIVITY` |
| STT не распознает речь | `DEEPGRAM_API_KEY`, сетевой доступ, выбранный микрофон |
| Не реагирует на `Alexandr` | `PORCUPINE_ACCESS_KEY`, микрофон, чувствительность `PORCUPINE_SENSITIVITY`, `AUDIO_INPUT_DEVICE_NAME` / `AUDIO_INPUT_DEVICE_INDEX` |
| STT не распознает речь | `DEEPGRAM_API_KEY`, сетевой доступ, выбранный микрофон, `AUDIO_INPUT_DEVICE_NAME` / `AUDIO_INPUT_DEVICE_INDEX` |
| Нет звука | корректное аудиоустройство и доступность `pactl`/`amixer` |
| Будильник/таймер не звонит | наличие `mpg123` в системе |
| Ошибка про несколько AI API | в `.env` должен остаться только один незакомментированный AI ключ |

View File

@@ -128,13 +128,16 @@ class SpeechRecognizer:
def _get_stream(self):
"""Открывает аудиопоток PyAudio, если он еще не открыт."""
if self.stream is None:
device_index, device_info = get_audio_manager().resolve_input_device()
self.stream = self.pa.open(
rate=SAMPLE_RATE,
channels=1,
format=pyaudio.paInt16,
input=True,
input_device_index=device_index,
frames_per_buffer=4096,
)
print(f"🎙️ STT input: {device_info.get('name', 'unknown')}")
return self.stream
async def _process_audio(

View File

@@ -54,14 +54,18 @@ class WakeWordDetector:
pass
# Открываем поток с параметрами, которые требует Porcupine
device_index, device_info = get_audio_manager().resolve_input_device()
self.audio_stream = self.pa.open(
rate=self.porcupine.sample_rate,
channels=1,
format=pyaudio.paInt16,
input=True,
input_device_index=device_index,
frames_per_buffer=self.porcupine.frame_length,
)
self._stream_closed = False
device_name = device_info.get("name", "unknown")
print(f"🎙️ Wake word input: {device_name}")
def stop_monitoring(self):
"""Явная остановка и закрытие потока (чтобы освободить микрофон для других задач)."""

View File

@@ -1,6 +1,8 @@
import pyaudio
import threading
from .config import AUDIO_INPUT_DEVICE_INDEX, AUDIO_INPUT_DEVICE_NAME
class AudioManager:
_instance = None
@@ -17,6 +19,35 @@ class AudioManager:
def get_pyaudio(self):
return self.pa
def resolve_input_device(self):
"""
Возвращает индекс и информацию о входном устройстве.
Если индекс/имя не заданы в .env, используем системное default устройство.
"""
if AUDIO_INPUT_DEVICE_INDEX is not None:
info = self.pa.get_device_info_by_index(AUDIO_INPUT_DEVICE_INDEX)
if info.get("maxInputChannels", 0) <= 0:
raise ValueError(
f"AUDIO_INPUT_DEVICE_INDEX={AUDIO_INPUT_DEVICE_INDEX} не является входным устройством."
)
return AUDIO_INPUT_DEVICE_INDEX, info
if AUDIO_INPUT_DEVICE_NAME:
wanted = AUDIO_INPUT_DEVICE_NAME.lower()
for index in range(self.pa.get_device_count()):
info = self.pa.get_device_info_by_index(index)
if info.get("maxInputChannels", 0) <= 0:
continue
name = str(info.get("name", ""))
if wanted in name.lower():
return index, info
raise ValueError(
f"Не найдено входное устройство по AUDIO_INPUT_DEVICE_NAME={AUDIO_INPUT_DEVICE_NAME!r}."
)
default_info = self.pa.get_default_input_device_info()
return None, default_info
def cleanup(self):
if self.pa:
self.pa.terminate()

View File

@@ -75,6 +75,12 @@ PORCUPINE_SENSITIVITY = float(os.getenv("PORCUPINE_SENSITIVITY", "0.8"))
# Частота дискретизации для микрофона (стандарт для распознавания речи)
SAMPLE_RATE = 16000
CHANNELS = 1
# Явный выбор входного устройства. Можно задать либо индекс, либо часть имени.
_audio_input_device_index = os.getenv("AUDIO_INPUT_DEVICE_INDEX", "").strip()
AUDIO_INPUT_DEVICE_INDEX = (
int(_audio_input_device_index) if _audio_input_device_index else None
)
AUDIO_INPUT_DEVICE_NAME = os.getenv("AUDIO_INPUT_DEVICE_NAME", "").strip() or None
# --- Настройка времени ---
# Устанавливаем часовой пояс на Москву, чтобы будильник работал корректно