audio_capture.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. """
  2. 音频采集模块
  3. 提供音频输入流管理,支持AEC回音消除
  4. """
  5. import asyncio
  6. import numpy as np
  7. import sounddevice as sd
  8. from typing import Callable, Optional, List
  9. from pathlib import Path
  10. class AudioListener:
  11. """音频监听器协议"""
  12. def on_audio_data(self, audio_data: np.ndarray) -> None:
  13. """接收音频数据的回调"""
  14. ...
  15. class AudioCapture:
  16. """音频采集器 - 支持AEC回音消除"""
  17. def __init__(self, sample_rate: int = 16000, channels: int = 1, device_id: Optional[int] = None, enable_aec: bool = False):
  18. """
  19. 初始化音频采集器
  20. Args:
  21. sample_rate: 采样率
  22. channels: 声道数
  23. device_id: 设备ID,None表示自动选择
  24. enable_aec: 是否启用AEC回音消除
  25. """
  26. self.sample_rate = sample_rate
  27. self.channels = channels
  28. self.device_id = device_id
  29. self.enable_aec = enable_aec
  30. self.input_stream = None
  31. self.frame_size = int(sample_rate * 0.02) # 20ms帧
  32. self._audio_listeners: List[AudioListener] = []
  33. self._is_closing = False
  34. # AEC处理器
  35. self.aec_processor = None
  36. def add_audio_listener(self, listener: AudioListener):
  37. """添加音频监听器"""
  38. if listener not in self._audio_listeners:
  39. self._audio_listeners.append(listener)
  40. def remove_audio_listener(self, listener: AudioListener):
  41. """移除音频监听器"""
  42. if listener in self._audio_listeners:
  43. self._audio_listeners.remove(listener)
  44. async def start(self):
  45. """启动音频采集"""
  46. try:
  47. # 初始化AEC处理器(如果启用)
  48. if self.enable_aec:
  49. try:
  50. from .aec_processor import AECProcessor
  51. self.aec_processor = AECProcessor(
  52. sample_rate=self.sample_rate,
  53. channels=self.channels
  54. )
  55. await self.aec_processor.initialize()
  56. print(f"AEC回音消除已启用")
  57. except Exception as e:
  58. print(f"AEC初始化失败,将使用原始音频: {e}")
  59. self.aec_processor = None
  60. # 自动选择设备
  61. if self.device_id is None:
  62. devices = sd.query_devices()
  63. input_devices = [
  64. (i, d) for i, d in enumerate(devices)
  65. if d['max_input_channels'] > 0
  66. ]
  67. if not input_devices:
  68. raise RuntimeError("找不到可用的输入设备")
  69. self.device_id = input_devices[0][0]
  70. print(f"自动选择输入设备: {input_devices[0][1]['name']}")
  71. # 创建输入流
  72. self.input_stream = sd.InputStream(
  73. device=self.device_id,
  74. samplerate=self.sample_rate,
  75. channels=self.channels,
  76. dtype=np.float32,
  77. blocksize=self.frame_size,
  78. callback=self._input_callback,
  79. latency="low",
  80. )
  81. self.input_stream.start()
  82. print(f"音频采集已启动: {self.sample_rate}Hz {self.channels}ch")
  83. except Exception as e:
  84. raise RuntimeError(f"启动音频采集失败: {e}")
  85. def _input_callback(self, indata, frames, time_info, status):
  86. """输入回调"""
  87. if status and "overflow" not in str(status).lower():
  88. print(f"输入流状态: {status}")
  89. if self._is_closing:
  90. return
  91. try:
  92. # 转换为 int16 格式
  93. audio_data_int16 = (indata.flatten() * 32768.0).astype(np.int16)
  94. # 应用AEC处理(如果启用)
  95. if self.aec_processor and self.aec_processor.is_initialized:
  96. audio_data_int16 = self.aec_processor.process_audio(audio_data_int16)
  97. # 通知所有监听器
  98. for listener in self._audio_listeners:
  99. try:
  100. listener.on_audio_data(audio_data_int16.copy())
  101. except Exception as e:
  102. print(f"音频监听器处理失败: {e}")
  103. except Exception as e:
  104. print(f"输入回调错误: {e}")
  105. async def stop(self):
  106. """停止音频采集"""
  107. self._is_closing = True
  108. # 停止AEC处理器(如果启用)
  109. if self.aec_processor:
  110. try:
  111. await self.aec_processor.close()
  112. except Exception as e:
  113. print(f"关闭AEC处理器失败: {e}")
  114. finally:
  115. self.aec_processor = None
  116. if self.input_stream:
  117. try:
  118. if self.input_stream.active:
  119. self.input_stream.stop()
  120. self.input_stream.close()
  121. except Exception as e:
  122. print(f"关闭音频流失败: {e}")
  123. finally:
  124. self.input_stream = None
  125. self._audio_listeners.clear()
  126. print("音频采集已停止")
  127. @staticmethod
  128. def list_devices():
  129. """列出所有可用设备"""
  130. devices = sd.query_devices()
  131. print("\n可用音频设备:")
  132. print("-" * 60)
  133. for i, device in enumerate(devices):
  134. if device['max_input_channels'] > 0:
  135. print(f"[{i}] {device['name']}")
  136. print(f" 输入声道: {device['max_input_channels']}, 采样率: {device['default_samplerate']}Hz")
  137. print("-" * 60)