diff --git a/main/xiaozhi-server/test/wakeword_runtime/core/microphone.py b/main/xiaozhi-server/test/wakeword_runtime/core/microphone.py index c376af29..5b15c29d 100644 --- a/main/xiaozhi-server/test/wakeword_runtime/core/microphone.py +++ b/main/xiaozhi-server/test/wakeword_runtime/core/microphone.py @@ -1,4 +1,6 @@ import logging +import threading +from collections import deque from typing import Protocol import numpy as np @@ -14,6 +16,8 @@ class AudioListener(Protocol): class MicrophoneListener: + _MAX_QUEUE_SIZE = 10 + def __init__(self, config: RuntimeConfig) -> None: self.config = config self._stream = None @@ -24,6 +28,9 @@ class MicrophoneListener: self._device = self.config.audio.input_device self._block_duration_ms = 100 self._block_size = int(self._sample_rate * (self._block_duration_ms / 1000)) + self._audio_queue: deque = deque(maxlen=self._MAX_QUEUE_SIZE) + self._queue_event = threading.Event() + self._consumer_thread = None def add_audio_listener(self, listener: AudioListener) -> None: if listener not in self._listeners: @@ -41,6 +48,13 @@ class MicrophoneListener: "Missing dependency: sounddevice. Install runtime dependencies before starting microphone listener." ) from exc + self._running = True + self._audio_queue.clear() + self._consumer_thread = threading.Thread( + target=self._consume_loop, daemon=True, name="mic-consumer" + ) + self._consumer_thread.start() + self._stream = sd.InputStream( device=self._device, samplerate=self._sample_rate, @@ -51,7 +65,6 @@ class MicrophoneListener: latency="low", ) self._stream.start() - self._running = True logger.info("microphone listener started") logger.info("microphone sample rate: %s", self._sample_rate) @@ -64,6 +77,10 @@ class MicrophoneListener: return self._running = False + self._queue_event.set() + if self._consumer_thread is not None: + self._consumer_thread.join(timeout=2) + self._consumer_thread = None if self._stream is not None: try: self._stream.stop() @@ -83,8 +100,17 @@ class MicrophoneListener: else: audio = audio.reshape(-1) - for listener in list(self._listeners): - try: - listener.on_audio_data(audio) - except Exception: - logger.exception("audio listener callback failed") + self._audio_queue.append(audio) + self._queue_event.set() + + def _consume_loop(self) -> None: + while self._running: + self._queue_event.wait(timeout=0.5) + self._queue_event.clear() + while self._audio_queue: + audio = self._audio_queue.popleft() + for listener in list(self._listeners): + try: + listener.on_audio_data(audio) + except Exception: + logger.exception("audio listener callback failed")