File size: 2,796 Bytes
60f8238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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)