liumaolin
refactor(core): Architecturally decouple Audio, ASR, and TTS modules
60f8238
import ctypes
import time
from multiprocessing import Queue
from voice_dialogue.config.paths import LIBRARIES_PATH
from voice_dialogue.utils.logger import logger
from .base_capture import BaseCapture
class AecCapture(BaseCapture):
"""
使用 macOS 原生库进行支持 AEC 的音频捕获策略。
"""
def __init__(self, audio_frames_queue: Queue, **kwargs):
super().__init__(audio_frames_queue=audio_frames_queue, **kwargs)
def _load_library(self):
"""加载并配置 AEC 原生库。"""
try:
audio_recorder = ctypes.CDLL(LIBRARIES_PATH / 'libAudioCapture.dylib')
audio_recorder.getAudioData.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_bool)]
audio_recorder.getAudioData.restype = ctypes.POINTER(ctypes.c_ubyte)
audio_recorder.freeAudioData.argtypes = [ctypes.POINTER(ctypes.c_ubyte)]
return audio_recorder
except Exception as e:
logger.error(f"加载 AEC 动态库失败: {e}")
raise
def _capture_loop(self, audio_recorder):
"""AEC 音频捕获的主循环。"""
logger.info("使用 AEC 音频捕获器开始采集...")
audio_recorder.startRecord()
self.is_ready = True
while not self.is_exited:
size = ctypes.c_int(0)
is_voice_active = ctypes.c_bool(False)
# 从原生库获取音频数据
data_ptr = audio_recorder.getAudioData(ctypes.byref(size), ctypes.byref(is_voice_active))
if data_ptr and size.value > 0:
audio_data = bytes(data_ptr[: size.value])
if not self.is_paused:
# 将音频帧和语音活动状态一同放入队列
self.audio_frames_queue.put((audio_data, is_voice_active.value))
# 释放原生库分配的内存
audio_recorder.freeAudioData(data_ptr)
else:
# 无数据时短暂休眠,避免CPU空转
time.sleep(0.01)
def _cleanup(self, audio_recorder):
"""清理 AEC 资源。"""
logger.info("停止 AEC 音频采集...")
if not audio_recorder:
return
audio_recorder.stopRecord()
def run(self):
"""
线程主循环,执行 AEC 音频捕获。
"""
audio_recorder = None
try:
audio_recorder = self._load_library()
self._capture_loop(audio_recorder)
except Exception as e:
logger.error(f'回声消除音频捕获器运行时发生错误: {e}')
# 如果 AEC 失败,这里可以考虑触发一个事件或回退机制,但目前只记录错误
finally:
self._cleanup(audio_recorder)