EG/plugins/user/voice_control/audio/audio_manager.py
2025-10-30 11:46:41 +08:00

752 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
音频管理器模块
负责管理音频输入输出设备
"""
import time
from typing import Dict, Any, List, Optional
import threading
class AudioManager:
"""
音频管理器
负责管理音频输入输出设备
"""
def __init__(self, plugin):
"""
初始化音频管理器
Args:
plugin: 语音控制插件实例
"""
self.plugin = plugin
self.enabled = False
self.initialized = False
# 音频设备列表
self.audio_devices = {
'input': {},
'output': {}
}
# 当前设备配置
self.current_devices = {
'input': None,
'output': None
}
# 音频流管理
self.audio_streams = {
'input': None,
'output': None
}
# 音频配置
self.audio_config = {
'sample_rate': 16000,
'channels': 1,
'format': 'int16',
'input_buffer_size': 1024,
'output_buffer_size': 1024,
'latency': 'low',
'enable_echo_cancellation': True,
'enable_noise_reduction': True,
'enable_auto_gain': True
}
# 音频状态
self.audio_state = {
'input_level': 0.0,
'output_level': 0.0,
'is_recording': False,
'is_playing': False,
'last_input_time': 0.0,
'last_output_time': 0.0
}
# 设备统计
self.device_stats = {
'devices_found': 0,
'input_devices': 0,
'output_devices': 0,
'devices_connected': 0,
'devices_disconnected': 0,
'data_transferred': 0
}
# 线程管理
self.monitor_thread = None
self.running = False
# 回调函数
self.audio_callbacks = {
'device_connected': [],
'device_disconnected': [],
'input_level_changed': [],
'output_level_changed': [],
'recording_started': [],
'recording_stopped': [],
'playback_started': [],
'playback_stopped': []
}
# 时间戳记录
self.last_device_scan = 0.0
self.last_level_update = 0.0
print("✓ 音频管理器已创建")
def initialize(self) -> bool:
"""
初始化音频管理器
Returns:
是否初始化成功
"""
try:
# 扫描音频设备
self._scan_audio_devices()
# 初始化默认设备
self._initialize_default_devices()
self.initialized = True
print("✓ 音频管理器初始化完成")
return True
except Exception as e:
print(f"✗ 音频管理器初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def enable(self) -> bool:
"""
启用音频管理器
Returns:
是否启用成功
"""
try:
if not self.initialized:
print("✗ 音频管理器未初始化")
return False
self.enabled = True
self.running = True
# 启动监控线程
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
self.monitor_thread.start()
print("✓ 音频管理器已启用")
return True
except Exception as e:
print(f"✗ 音频管理器启用失败: {e}")
import traceback
traceback.print_exc()
return False
def disable(self):
"""禁用音频管理器"""
try:
self.enabled = False
self.running = False
# 等待监控线程结束
if self.monitor_thread and self.monitor_thread.is_alive():
self.monitor_thread.join(timeout=2.0)
# 关闭所有音频流
self._close_all_streams()
print("✓ 音频管理器已禁用")
except Exception as e:
print(f"✗ 音频管理器禁用失败: {e}")
import traceback
traceback.print_exc()
def finalize(self):
"""清理音频管理器资源"""
try:
self.disable()
self.audio_callbacks.clear()
self.audio_devices.clear()
self.initialized = False
print("✓ 音频管理器资源已清理")
except Exception as e:
print(f"✗ 音频管理器资源清理失败: {e}")
import traceback
traceback.print_exc()
def update(self, dt: float):
"""
更新音频管理器状态
Args:
dt: 时间增量
"""
try:
if not self.enabled:
return
current_time = time.time()
# 定期扫描设备
if current_time - self.last_device_scan > 5.0: # 每5秒扫描一次
self._scan_audio_devices()
self.last_device_scan = current_time
self.last_level_update = current_time
except Exception as e:
print(f"✗ 音频管理器更新失败: {e}")
import traceback
traceback.print_exc()
def _scan_audio_devices(self):
"""扫描音频设备"""
try:
# 模拟设备扫描
# 在实际实现中这里会调用平台特定的API来发现音频设备
# 模拟输入设备
input_devices = {
0: {
'id': 0,
'name': '系统默认麦克风',
'type': 'input',
'channels': 1,
'sample_rates': [16000, 44100, 48000],
'is_default': True,
'connected': True
},
1: {
'id': 1,
'name': 'USB 麦克风',
'type': 'input',
'channels': 2,
'sample_rates': [16000, 44100, 48000],
'is_default': False,
'connected': True
},
2: {
'id': 2,
'name': '蓝牙耳机麦克风',
'type': 'input',
'channels': 1,
'sample_rates': [16000, 44100],
'is_default': False,
'connected': True
}
}
# 模拟输出设备
output_devices = {
0: {
'id': 0,
'name': '系统默认扬声器',
'type': 'output',
'channels': 2,
'sample_rates': [16000, 44100, 48000],
'is_default': True,
'connected': True
},
1: {
'id': 1,
'name': 'USB 音响',
'type': 'output',
'channels': 2,
'sample_rates': [16000, 44100, 48000, 96000],
'is_default': False,
'connected': True
},
2: {
'id': 2,
'name': '蓝牙耳机',
'type': 'output',
'channels': 2,
'sample_rates': [16000, 44100],
'is_default': False,
'connected': True
}
}
# 更新设备列表
self.audio_devices['input'] = input_devices
self.audio_devices['output'] = output_devices
# 更新统计信息
self.device_stats['devices_found'] = len(input_devices) + len(output_devices)
self.device_stats['input_devices'] = len(input_devices)
self.device_stats['output_devices'] = len(output_devices)
print(f"✓ 音频设备扫描完成: {len(input_devices)}个输入设备, {len(output_devices)}个输出设备")
except Exception as e:
print(f"✗ 音频设备扫描失败: {e}")
def _initialize_default_devices(self):
"""初始化默认设备"""
try:
# 查找默认输入设备
for device_id, device_info in self.audio_devices['input'].items():
if device_info.get('is_default', False):
self.current_devices['input'] = device_id
break
# 查找默认输出设备
for device_id, device_info in self.audio_devices['output'].items():
if device_info.get('is_default', False):
self.current_devices['output'] = device_id
break
print("✓ 默认音频设备初始化完成")
except Exception as e:
print(f"✗ 默认音频设备初始化失败: {e}")
def _monitor_loop(self):
"""监控循环线程"""
try:
monitor_interval = 0.1 # 100ms
while self.running:
if self.enabled:
self._update_audio_levels()
self._check_device_status()
time.sleep(monitor_interval)
except Exception as e:
print(f"✗ 音频监控循环失败: {e}")
def _update_audio_levels(self):
"""更新音频级别"""
try:
current_time = time.time()
# 模拟输入级别更新
if self.audio_state['is_recording']:
import random
new_input_level = random.uniform(0.0, 1.0)
if abs(new_input_level - self.audio_state['input_level']) > 0.01:
self.audio_state['input_level'] = new_input_level
self._trigger_audio_callback('input_level_changed', {
'level': new_input_level,
'timestamp': current_time
})
# 模拟输出级别更新
if self.audio_state['is_playing']:
import random
new_output_level = random.uniform(0.0, 1.0)
if abs(new_output_level - self.audio_state['output_level']) > 0.01:
self.audio_state['output_level'] = new_output_level
self._trigger_audio_callback('output_level_changed', {
'level': new_output_level,
'timestamp': current_time
})
self.last_level_update = current_time
except Exception as e:
print(f"✗ 音频级别更新失败: {e}")
def _check_device_status(self):
"""检查设备状态"""
try:
# 在实际实现中,这里会检查设备连接状态
# 目前只是模拟
pass
except Exception as e:
print(f"✗ 设备状态检查失败: {e}")
def _close_all_streams(self):
"""关闭所有音频流"""
try:
self.audio_streams['input'] = None
self.audio_streams['output'] = None
print("✓ 所有音频流已关闭")
except Exception as e:
print(f"✗ 音频流关闭失败: {e}")
def get_input_devices(self) -> Dict[int, Dict[str, Any]]:
"""
获取输入设备列表
Returns:
输入设备字典
"""
return self.audio_devices['input'].copy()
def get_output_devices(self) -> Dict[int, Dict[str, Any]]:
"""
获取输出设备列表
Returns:
输出设备字典
"""
return self.audio_devices['output'].copy()
def get_all_devices(self) -> Dict[str, Dict[int, Dict[str, Any]]]:
"""
获取所有设备列表
Returns:
所有设备字典
"""
return {
'input': self.audio_devices['input'].copy(),
'output': self.audio_devices['output'].copy()
}
def set_input_device(self, device_id: int) -> bool:
"""
设置输入设备
Args:
device_id: 设备ID
Returns:
是否设置成功
"""
try:
if device_id in self.audio_devices['input']:
old_device = self.current_devices['input']
self.current_devices['input'] = device_id
print(f"✓ 输入设备已设置: {self.audio_devices['input'][device_id]['name']}")
# 触发设备变更回调
self._trigger_audio_callback('device_connected', {
'device_type': 'input',
'device_id': device_id,
'device_name': self.audio_devices['input'][device_id]['name'],
'old_device_id': old_device
})
return True
else:
print(f"✗ 输入设备不存在: ID {device_id}")
return False
except Exception as e:
print(f"✗ 输入设备设置失败: {e}")
return False
def set_output_device(self, device_id: int) -> bool:
"""
设置输出设备
Args:
device_id: 设备ID
Returns:
是否设置成功
"""
try:
if device_id in self.audio_devices['output']:
old_device = self.current_devices['output']
self.current_devices['output'] = device_id
print(f"✓ 输出设备已设置: {self.audio_devices['output'][device_id]['name']}")
# 触发设备变更回调
self._trigger_audio_callback('device_connected', {
'device_type': 'output',
'device_id': device_id,
'device_name': self.audio_devices['output'][device_id]['name'],
'old_device_id': old_device
})
return True
else:
print(f"✗ 输出设备不存在: ID {device_id}")
return False
except Exception as e:
print(f"✗ 输出设备设置失败: {e}")
return False
def get_current_input_device(self) -> Optional[Dict[str, Any]]:
"""
获取当前输入设备
Returns:
当前输入设备信息或None
"""
try:
device_id = self.current_devices['input']
if device_id is not None and device_id in self.audio_devices['input']:
return self.audio_devices['input'][device_id]
return None
except Exception as e:
print(f"✗ 当前输入设备获取失败: {e}")
return None
def get_current_output_device(self) -> Optional[Dict[str, Any]]:
"""
获取当前输出设备
Returns:
当前输出设备信息或None
"""
try:
device_id = self.current_devices['output']
if device_id is not None and device_id in self.audio_devices['output']:
return self.audio_devices['output'][device_id]
return None
except Exception as e:
print(f"✗ 当前输出设备获取失败: {e}")
return None
def start_recording(self) -> bool:
"""
开始录音
Returns:
是否开始成功
"""
try:
if not self.enabled:
print("✗ 音频管理器未启用")
return False
# 模拟开始录音
self.audio_state['is_recording'] = True
self.audio_state['last_input_time'] = time.time()
# 触发录音开始回调
self._trigger_audio_callback('recording_started', {
'device_id': self.current_devices['input'],
'timestamp': time.time()
})
print("✓ 录音已开始")
return True
except Exception as e:
print(f"✗ 录音开始失败: {e}")
return False
def stop_recording(self) -> bool:
"""
停止录音
Returns:
是否停止成功
"""
try:
self.audio_state['is_recording'] = False
# 触发录音停止回调
self._trigger_audio_callback('recording_stopped', {
'device_id': self.current_devices['input'],
'timestamp': time.time()
})
print("✓ 录音已停止")
return True
except Exception as e:
print(f"✗ 录音停止失败: {e}")
return False
def start_playback(self) -> bool:
"""
开始播放
Returns:
是否开始成功
"""
try:
if not self.enabled:
print("✗ 音频管理器未启用")
return False
# 模拟开始播放
self.audio_state['is_playing'] = True
self.audio_state['last_output_time'] = time.time()
# 触发播放开始回调
self._trigger_audio_callback('playback_started', {
'device_id': self.current_devices['output'],
'timestamp': time.time()
})
print("✓ 播放已开始")
return True
except Exception as e:
print(f"✗ 播放开始失败: {e}")
return False
def stop_playback(self) -> bool:
"""
停止播放
Returns:
是否停止成功
"""
try:
self.audio_state['is_playing'] = False
# 触发播放停止回调
self._trigger_audio_callback('playback_stopped', {
'device_id': self.current_devices['output'],
'timestamp': time.time()
})
print("✓ 播放已停止")
return True
except Exception as e:
print(f"✗ 播放停止失败: {e}")
return False
def get_audio_level(self, stream_type: str) -> float:
"""
获取音频级别
Args:
stream_type: 流类型('input''output')
Returns:
音频级别(0.0-1.0)
"""
try:
if stream_type == 'input':
return self.audio_state['input_level']
elif stream_type == 'output':
return self.audio_state['output_level']
else:
print(f"✗ 无效的流类型: {stream_type}")
return 0.0
except Exception as e:
print(f"✗ 音频级别获取失败: {e}")
return 0.0
def set_audio_config(self, config: Dict[str, Any]) -> bool:
"""
设置音频配置
Args:
config: 配置字典
Returns:
是否设置成功
"""
try:
self.audio_config.update(config)
print(f"✓ 音频配置已更新: {self.audio_config}")
return True
except Exception as e:
print(f"✗ 音频配置设置失败: {e}")
return False
def get_audio_config(self) -> Dict[str, Any]:
"""
获取音频配置
Returns:
配置字典
"""
return self.audio_config.copy()
def is_recording(self) -> bool:
"""
检查是否正在录音
Returns:
是否正在录音
"""
return self.audio_state['is_recording']
def is_playing(self) -> bool:
"""
检查是否正在播放
Returns:
是否正在播放
"""
return self.audio_state['is_playing']
def _trigger_audio_callback(self, callback_type: str, data: Dict[str, Any]):
"""
触发音频回调
Args:
callback_type: 回调类型
data: 回调数据
"""
try:
if callback_type in self.audio_callbacks:
for callback in self.audio_callbacks[callback_type]:
try:
callback(data)
except Exception as e:
print(f"✗ 音频回调执行失败: {e}")
except Exception as e:
print(f"✗ 音频回调触发失败: {e}")
def register_audio_callback(self, callback_type: str, callback: callable):
"""
注册音频回调
Args:
callback_type: 回调类型
callback: 回调函数
"""
try:
if callback_type in self.audio_callbacks:
self.audio_callbacks[callback_type].append(callback)
print(f"✓ 音频回调已注册: {callback_type}")
else:
print(f"✗ 无效的回调类型: {callback_type}")
except Exception as e:
print(f"✗ 音频回调注册失败: {e}")
def unregister_audio_callback(self, callback_type: str, callback: callable):
"""
注销音频回调
Args:
callback_type: 回调类型
callback: 回调函数
"""
try:
if callback_type in self.audio_callbacks:
if callback in self.audio_callbacks[callback_type]:
self.audio_callbacks[callback_type].remove(callback)
print(f"✓ 音频回调已注销: {callback_type}")
except Exception as e:
print(f"✗ 音频回调注销失败: {e}")
def get_device_stats(self) -> Dict[str, int]:
"""
获取设备统计信息
Returns:
统计信息字典
"""
return self.device_stats.copy()
def reset_device_stats(self):
"""重置设备统计信息"""
try:
self.device_stats = {
'devices_found': 0,
'input_devices': 0,
'output_devices': 0,
'devices_connected': 0,
'devices_disconnected': 0,
'data_transferred': 0
}
print("✓ 音频管理器统计信息已重置")
except Exception as e:
print(f"✗ 音频管理器统计信息重置失败: {e}")