Add configurable input device selection
This commit is contained in:
@@ -30,6 +30,8 @@ ANTHROPIC_API_VERSION=2023-06-01
|
|||||||
DEEPGRAM_API_KEY=your_deepgram_api_key_here
|
DEEPGRAM_API_KEY=your_deepgram_api_key_here
|
||||||
PORCUPINE_ACCESS_KEY=your_porcupine_access_key_here
|
PORCUPINE_ACCESS_KEY=your_porcupine_access_key_here
|
||||||
PORCUPINE_SENSITIVITY=0.8
|
PORCUPINE_SENSITIVITY=0.8
|
||||||
|
AUDIO_INPUT_DEVICE_INDEX=
|
||||||
|
AUDIO_INPUT_DEVICE_NAME=
|
||||||
TTS_EN_SPEAKER=en_0
|
TTS_EN_SPEAKER=en_0
|
||||||
WEATHER_LAT=63.56
|
WEATHER_LAT=63.56
|
||||||
WEATHER_LON=53.69
|
WEATHER_LON=53.69
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ python run.py
|
|||||||
| `DEEPGRAM_API_KEY` | Да | - | Ключ Deepgram STT |
|
| `DEEPGRAM_API_KEY` | Да | - | Ключ Deepgram STT |
|
||||||
| `PORCUPINE_ACCESS_KEY` | Да | - | Ключ PicoVoice Porcupine |
|
| `PORCUPINE_ACCESS_KEY` | Да | - | Ключ PicoVoice Porcupine |
|
||||||
| `PORCUPINE_SENSITIVITY` | Нет | `0.8` | Чувствительность wake word |
|
| `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 |
|
| `TTS_EN_SPEAKER` | Нет | `en_0` | Английский голос TTS |
|
||||||
| `WEATHER_LAT` | Нет | - | Широта города по умолчанию |
|
| `WEATHER_LAT` | Нет | - | Широта города по умолчанию |
|
||||||
| `WEATHER_LON` | Нет | - | Долгота города по умолчанию |
|
| `WEATHER_LON` | Нет | - | Долгота города по умолчанию |
|
||||||
@@ -206,8 +208,8 @@ alexander_smart-speaker/
|
|||||||
|
|
||||||
| Проблема | Что проверить |
|
| Проблема | Что проверить |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Не реагирует на `Alexandr` | `PORCUPINE_ACCESS_KEY`, микрофон, чувствительность `PORCUPINE_SENSITIVITY` |
|
| Не реагирует на `Alexandr` | `PORCUPINE_ACCESS_KEY`, микрофон, чувствительность `PORCUPINE_SENSITIVITY`, `AUDIO_INPUT_DEVICE_NAME` / `AUDIO_INPUT_DEVICE_INDEX` |
|
||||||
| STT не распознает речь | `DEEPGRAM_API_KEY`, сетевой доступ, выбранный микрофон |
|
| STT не распознает речь | `DEEPGRAM_API_KEY`, сетевой доступ, выбранный микрофон, `AUDIO_INPUT_DEVICE_NAME` / `AUDIO_INPUT_DEVICE_INDEX` |
|
||||||
| Нет звука | корректное аудиоустройство и доступность `pactl`/`amixer` |
|
| Нет звука | корректное аудиоустройство и доступность `pactl`/`amixer` |
|
||||||
| Будильник/таймер не звонит | наличие `mpg123` в системе |
|
| Будильник/таймер не звонит | наличие `mpg123` в системе |
|
||||||
| Ошибка про несколько AI API | в `.env` должен остаться только один незакомментированный AI ключ |
|
| Ошибка про несколько AI API | в `.env` должен остаться только один незакомментированный AI ключ |
|
||||||
|
|||||||
@@ -128,13 +128,16 @@ class SpeechRecognizer:
|
|||||||
def _get_stream(self):
|
def _get_stream(self):
|
||||||
"""Открывает аудиопоток PyAudio, если он еще не открыт."""
|
"""Открывает аудиопоток PyAudio, если он еще не открыт."""
|
||||||
if self.stream is None:
|
if self.stream is None:
|
||||||
|
device_index, device_info = get_audio_manager().resolve_input_device()
|
||||||
self.stream = self.pa.open(
|
self.stream = self.pa.open(
|
||||||
rate=SAMPLE_RATE,
|
rate=SAMPLE_RATE,
|
||||||
channels=1,
|
channels=1,
|
||||||
format=pyaudio.paInt16,
|
format=pyaudio.paInt16,
|
||||||
input=True,
|
input=True,
|
||||||
|
input_device_index=device_index,
|
||||||
frames_per_buffer=4096,
|
frames_per_buffer=4096,
|
||||||
)
|
)
|
||||||
|
print(f"🎙️ STT input: {device_info.get('name', 'unknown')}")
|
||||||
return self.stream
|
return self.stream
|
||||||
|
|
||||||
async def _process_audio(
|
async def _process_audio(
|
||||||
|
|||||||
@@ -54,14 +54,18 @@ class WakeWordDetector:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Открываем поток с параметрами, которые требует Porcupine
|
# Открываем поток с параметрами, которые требует Porcupine
|
||||||
|
device_index, device_info = get_audio_manager().resolve_input_device()
|
||||||
self.audio_stream = self.pa.open(
|
self.audio_stream = self.pa.open(
|
||||||
rate=self.porcupine.sample_rate,
|
rate=self.porcupine.sample_rate,
|
||||||
channels=1,
|
channels=1,
|
||||||
format=pyaudio.paInt16,
|
format=pyaudio.paInt16,
|
||||||
input=True,
|
input=True,
|
||||||
|
input_device_index=device_index,
|
||||||
frames_per_buffer=self.porcupine.frame_length,
|
frames_per_buffer=self.porcupine.frame_length,
|
||||||
)
|
)
|
||||||
self._stream_closed = False
|
self._stream_closed = False
|
||||||
|
device_name = device_info.get("name", "unknown")
|
||||||
|
print(f"🎙️ Wake word input: {device_name}")
|
||||||
|
|
||||||
def stop_monitoring(self):
|
def stop_monitoring(self):
|
||||||
"""Явная остановка и закрытие потока (чтобы освободить микрофон для других задач)."""
|
"""Явная остановка и закрытие потока (чтобы освободить микрофон для других задач)."""
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import pyaudio
|
import pyaudio
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from .config import AUDIO_INPUT_DEVICE_INDEX, AUDIO_INPUT_DEVICE_NAME
|
||||||
|
|
||||||
|
|
||||||
class AudioManager:
|
class AudioManager:
|
||||||
_instance = None
|
_instance = None
|
||||||
@@ -17,6 +19,35 @@ class AudioManager:
|
|||||||
def get_pyaudio(self):
|
def get_pyaudio(self):
|
||||||
return self.pa
|
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):
|
def cleanup(self):
|
||||||
if self.pa:
|
if self.pa:
|
||||||
self.pa.terminate()
|
self.pa.terminate()
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ PORCUPINE_SENSITIVITY = float(os.getenv("PORCUPINE_SENSITIVITY", "0.8"))
|
|||||||
# Частота дискретизации для микрофона (стандарт для распознавания речи)
|
# Частота дискретизации для микрофона (стандарт для распознавания речи)
|
||||||
SAMPLE_RATE = 16000
|
SAMPLE_RATE = 16000
|
||||||
CHANNELS = 1
|
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
|
||||||
|
|
||||||
# --- Настройка времени ---
|
# --- Настройка времени ---
|
||||||
# Устанавливаем часовой пояс на Москву, чтобы будильник работал корректно
|
# Устанавливаем часовой пояс на Москву, чтобы будильник работал корректно
|
||||||
|
|||||||
Reference in New Issue
Block a user