707 lines
24 KiB
Python
707 lines
24 KiB
Python
"""
|
|
语音识别器模块
|
|
负责实时语音识别功能
|
|
"""
|
|
|
|
import time
|
|
import threading
|
|
import queue
|
|
import math
|
|
from typing import Dict, Any, List, Optional
|
|
|
|
class SpeechRecognizer:
|
|
"""
|
|
语音识别器
|
|
负责实时语音识别功能
|
|
"""
|
|
|
|
def __init__(self, plugin):
|
|
"""
|
|
初始化语音识别器
|
|
|
|
Args:
|
|
plugin: 语音控制插件实例
|
|
"""
|
|
self.plugin = plugin
|
|
self.enabled = False
|
|
self.initialized = False
|
|
|
|
# 音频输入配置
|
|
self.audio_config = {
|
|
'sample_rate': 16000,
|
|
'channels': 1,
|
|
'format': 'int16',
|
|
'chunk_size': 1024,
|
|
'buffer_size': 4096
|
|
}
|
|
|
|
# 识别配置
|
|
self.recognition_config = {
|
|
'language': 'zh-CN',
|
|
'enable_keyword_spotting': True,
|
|
'enable_continuous_recognition': True,
|
|
'silence_threshold': 0.01,
|
|
'speech_threshold': 0.02,
|
|
'silence_duration': 1.0,
|
|
'max_recording_duration': 10.0
|
|
}
|
|
|
|
# 音频缓冲区
|
|
self.audio_buffer = queue.Queue(maxsize=100)
|
|
self.processed_buffer = queue.Queue(maxsize=10)
|
|
|
|
# 识别状态
|
|
self.recognition_state = {
|
|
'is_recording': False,
|
|
'is_processing': False,
|
|
'is_speech_detected': False,
|
|
'last_speech_time': 0.0,
|
|
'recording_start_time': 0.0,
|
|
'audio_level': 0.0
|
|
}
|
|
|
|
# 麦克风管理
|
|
self.microphone = None
|
|
self.audio_stream = None
|
|
|
|
# 线程管理
|
|
self.recording_thread = None
|
|
self.processing_thread = None
|
|
self.running = False
|
|
|
|
# 统计信息
|
|
self.stats = {
|
|
'audio_chunks_processed': 0,
|
|
'speech_segments_detected': 0,
|
|
'recognition_attempts': 0,
|
|
'successful_recognitions': 0,
|
|
'failed_recognitions': 0,
|
|
'processing_time_total': 0.0
|
|
}
|
|
|
|
# 回调函数
|
|
self.recognition_callbacks = {
|
|
'audio_chunk_received': [],
|
|
'speech_detected': [],
|
|
'silence_detected': [],
|
|
'recognition_result': [],
|
|
'recognition_error': []
|
|
}
|
|
|
|
# 时间戳记录
|
|
self.last_processing_time = 0.0
|
|
self.last_recognition_time = 0.0
|
|
|
|
print("✓ 语音识别器已创建")
|
|
|
|
def initialize(self) -> bool:
|
|
"""
|
|
初始化语音识别器
|
|
|
|
Returns:
|
|
是否初始化成功
|
|
"""
|
|
try:
|
|
# 初始化音频输入设备
|
|
self._initialize_audio_input()
|
|
|
|
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.recording_thread = threading.Thread(target=self._recording_loop, daemon=True)
|
|
self.recording_thread.start()
|
|
|
|
# 启动处理线程
|
|
self.processing_thread = threading.Thread(target=self._processing_loop, daemon=True)
|
|
self.processing_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.recording_thread and self.recording_thread.is_alive():
|
|
self.recording_thread.join(timeout=2.0)
|
|
|
|
if self.processing_thread and self.processing_thread.is_alive():
|
|
self.processing_thread.join(timeout=2.0)
|
|
|
|
# 停止音频流
|
|
self._stop_audio_stream()
|
|
|
|
print("✓ 语音识别器已禁用")
|
|
|
|
except Exception as e:
|
|
print(f"✗ 语音识别器禁用失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def finalize(self):
|
|
"""清理语音识别器资源"""
|
|
try:
|
|
self.disable()
|
|
self.recognition_callbacks.clear()
|
|
|
|
# 清理音频缓冲区
|
|
while not self.audio_buffer.empty():
|
|
try:
|
|
self.audio_buffer.get_nowait()
|
|
except queue.Empty:
|
|
break
|
|
|
|
while not self.processed_buffer.empty():
|
|
try:
|
|
self.processed_buffer.get_nowait()
|
|
except queue.Empty:
|
|
break
|
|
|
|
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()
|
|
self.last_processing_time = current_time
|
|
|
|
except Exception as e:
|
|
print(f"✗ 语音识别器更新失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def _initialize_audio_input(self):
|
|
"""初始化音频输入设备"""
|
|
try:
|
|
# 这里应该初始化实际的音频输入设备
|
|
# 由于这是一个模拟实现,我们只打印信息
|
|
print("✓ 音频输入设备初始化完成")
|
|
print(f" 采样率: {self.audio_config['sample_rate']} Hz")
|
|
print(f" 声道数: {self.audio_config['channels']}")
|
|
print(f" 格式: {self.audio_config['format']}")
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频输入设备初始化失败: {e}")
|
|
|
|
def _start_audio_stream(self) -> bool:
|
|
"""
|
|
启动音频流
|
|
|
|
Returns:
|
|
是否启动成功
|
|
"""
|
|
try:
|
|
# 这里应该启动实际的音频流
|
|
# 由于这是一个模拟实现,我们只设置状态
|
|
self.audio_stream = "simulated_audio_stream"
|
|
print("✓ 音频流已启动")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ 音频流启动失败: {e}")
|
|
return False
|
|
|
|
def _stop_audio_stream(self):
|
|
"""停止音频流"""
|
|
try:
|
|
# 这里应该停止实际的音频流
|
|
self.audio_stream = None
|
|
print("✓ 音频流已停止")
|
|
except Exception as e:
|
|
print(f"✗ 音频流停止失败: {e}")
|
|
|
|
def _recording_loop(self):
|
|
"""录音循环线程"""
|
|
try:
|
|
# 启动音频流
|
|
if not self._start_audio_stream():
|
|
return
|
|
|
|
chunk_duration = self.audio_config['chunk_size'] / self.audio_config['sample_rate']
|
|
|
|
while self.running and self.enabled:
|
|
try:
|
|
# 模拟读取音频数据
|
|
audio_chunk = self._read_audio_chunk()
|
|
if audio_chunk is not None:
|
|
# 计算音频级别
|
|
audio_level = self._calculate_audio_level(audio_chunk)
|
|
|
|
# 更新状态
|
|
self.recognition_state['audio_level'] = audio_level
|
|
|
|
# 检测语音活动
|
|
is_speech = audio_level > self.recognition_config['speech_threshold']
|
|
was_speech = self.recognition_state['is_speech_detected']
|
|
|
|
# 更新语音检测状态
|
|
self.recognition_state['is_speech_detected'] = is_speech
|
|
if is_speech:
|
|
self.recognition_state['last_speech_time'] = time.time()
|
|
|
|
# 触发回调
|
|
self._trigger_recognition_callback('audio_chunk_received', {
|
|
'audio_chunk': audio_chunk,
|
|
'audio_level': audio_level,
|
|
'is_speech': is_speech
|
|
})
|
|
|
|
# 检测语音/静音转换
|
|
if is_speech and not was_speech:
|
|
self._trigger_recognition_callback('speech_detected', {
|
|
'audio_level': audio_level
|
|
})
|
|
elif not is_speech and was_speech:
|
|
self._trigger_recognition_callback('silence_detected', {
|
|
'audio_level': audio_level
|
|
})
|
|
|
|
# 添加到缓冲区
|
|
try:
|
|
self.audio_buffer.put_nowait({
|
|
'data': audio_chunk,
|
|
'level': audio_level,
|
|
'timestamp': time.time()
|
|
})
|
|
self.stats['audio_chunks_processed'] += 1
|
|
except queue.Full:
|
|
# 缓冲区满,丢弃最旧的数据
|
|
try:
|
|
self.audio_buffer.get_nowait()
|
|
self.audio_buffer.put_nowait({
|
|
'data': audio_chunk,
|
|
'level': audio_level,
|
|
'timestamp': time.time()
|
|
})
|
|
except queue.Empty:
|
|
pass
|
|
|
|
# 控制录音频率
|
|
time.sleep(chunk_duration)
|
|
|
|
except Exception as e:
|
|
print(f"✗ 录音循环错误: {e}")
|
|
time.sleep(0.1)
|
|
|
|
except Exception as e:
|
|
print(f"✗ 录音循环失败: {e}")
|
|
|
|
def _processing_loop(self):
|
|
"""处理循环线程"""
|
|
try:
|
|
while self.running and self.enabled:
|
|
try:
|
|
# 从缓冲区获取音频数据
|
|
if not self.audio_buffer.empty():
|
|
audio_data = self.audio_buffer.get_nowait()
|
|
self._process_audio_chunk(audio_data)
|
|
|
|
# 控制处理频率
|
|
time.sleep(0.01)
|
|
|
|
except queue.Empty:
|
|
time.sleep(0.01)
|
|
except Exception as e:
|
|
print(f"✗ 处理循环错误: {e}")
|
|
time.sleep(0.1)
|
|
|
|
except Exception as e:
|
|
print(f"✗ 处理循环失败: {e}")
|
|
|
|
def _read_audio_chunk(self) -> Optional[bytes]:
|
|
"""
|
|
读取音频块
|
|
|
|
Returns:
|
|
音频数据或None
|
|
"""
|
|
try:
|
|
# 模拟生成音频数据
|
|
import random
|
|
import struct
|
|
|
|
chunk_size = self.audio_config['chunk_size']
|
|
audio_data = bytearray()
|
|
|
|
for _ in range(chunk_size):
|
|
# 生成模拟的音频样本
|
|
sample = int(random.randint(-32768, 32767) * 0.1)
|
|
audio_data.extend(struct.pack('<h', sample))
|
|
|
|
return bytes(audio_data)
|
|
except Exception as e:
|
|
print(f"✗ 音频块读取失败: {e}")
|
|
return None
|
|
|
|
def _calculate_audio_level(self, audio_chunk: bytes) -> float:
|
|
"""
|
|
计算音频级别
|
|
|
|
Args:
|
|
audio_chunk: 音频数据块
|
|
|
|
Returns:
|
|
音频级别(0.0-1.0)
|
|
"""
|
|
try:
|
|
import struct
|
|
|
|
# 解析音频数据
|
|
samples = struct.unpack(f'<{len(audio_chunk)//2}h', audio_chunk)
|
|
|
|
# 计算RMS
|
|
if len(samples) > 0:
|
|
rms = math.sqrt(sum(sample**2 for sample in samples) / len(samples))
|
|
# 归一化到0-1范围(假设16位音频)
|
|
level = rms / 32768.0
|
|
return min(1.0, level)
|
|
else:
|
|
return 0.0
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频级别计算失败: {e}")
|
|
return 0.0
|
|
|
|
def _process_audio_chunk(self, audio_data: Dict[str, Any]):
|
|
"""
|
|
处理音频块
|
|
|
|
Args:
|
|
audio_data: 音频数据字典
|
|
"""
|
|
try:
|
|
start_time = time.time()
|
|
|
|
# 模拟语音识别处理
|
|
processing_result = self._simulate_recognition(audio_data)
|
|
|
|
if processing_result:
|
|
# 添加到处理结果缓冲区
|
|
try:
|
|
self.processed_buffer.put_nowait(processing_result)
|
|
except queue.Full:
|
|
try:
|
|
self.processed_buffer.get_nowait()
|
|
self.processed_buffer.put_nowait(processing_result)
|
|
except queue.Empty:
|
|
pass
|
|
|
|
# 触发识别结果回调
|
|
self._trigger_recognition_callback('recognition_result', processing_result)
|
|
|
|
# 传递给语音管理器处理
|
|
if self.plugin.voice_manager:
|
|
self.plugin.voice_manager.process_speech(
|
|
processing_result['text'],
|
|
processing_result['confidence']
|
|
)
|
|
|
|
self.stats['successful_recognitions'] += 1
|
|
|
|
# 更新统计信息
|
|
processing_time = time.time() - start_time
|
|
self.stats['processing_time_total'] += processing_time
|
|
self.stats['recognition_attempts'] += 1
|
|
self.last_recognition_time = time.time()
|
|
|
|
except Exception as e:
|
|
print(f"✗ 音频块处理失败: {e}")
|
|
self.stats['failed_recognitions'] += 1
|
|
|
|
# 触发错误回调
|
|
self._trigger_recognition_callback('recognition_error', {
|
|
'error': str(e),
|
|
'timestamp': time.time()
|
|
})
|
|
|
|
def _simulate_recognition(self, audio_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
模拟语音识别
|
|
|
|
Args:
|
|
audio_data: 音频数据
|
|
|
|
Returns:
|
|
识别结果或None
|
|
"""
|
|
try:
|
|
# 基于音频级别和时间模拟识别结果
|
|
audio_level = audio_data.get('level', 0.0)
|
|
timestamp = audio_data.get('timestamp', time.time())
|
|
|
|
# 只有在音频级别足够高时才模拟识别
|
|
if audio_level > self.recognition_config['speech_threshold']:
|
|
import random
|
|
|
|
# 模拟识别文本
|
|
sample_texts = [
|
|
"前进", "后退", "左转", "右转", "停止",
|
|
"打开灯光", "关闭灯光", "增加音量", "减少音量",
|
|
"播放音乐", "暂停播放", "下一首", "上一首",
|
|
"你好", "谢谢", "再见", "帮助"
|
|
]
|
|
|
|
# 根据音频级别设置置信度
|
|
confidence = min(1.0, audio_level * 2) # 简单映射
|
|
|
|
# 有一定概率生成识别结果
|
|
if random.random() < 0.1: # 10%概率
|
|
text = random.choice(sample_texts)
|
|
return {
|
|
'text': text,
|
|
'confidence': confidence,
|
|
'timestamp': timestamp,
|
|
'audio_level': audio_level
|
|
}
|
|
|
|
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.recognition_state['is_recording'] = True
|
|
self.recognition_state['recording_start_time'] = time.time()
|
|
|
|
print("✓ 录音已开始")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"✗ 录音开始失败: {e}")
|
|
return False
|
|
|
|
def stop_recording(self) -> bool:
|
|
"""
|
|
停止录音
|
|
|
|
Returns:
|
|
是否停止成功
|
|
"""
|
|
try:
|
|
self.recognition_state['is_recording'] = False
|
|
print("✓ 录音已停止")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"✗ 录音停止失败: {e}")
|
|
return False
|
|
|
|
def is_recording(self) -> bool:
|
|
"""
|
|
检查是否正在录音
|
|
|
|
Returns:
|
|
是否正在录音
|
|
"""
|
|
return self.recognition_state['is_recording']
|
|
|
|
def is_speech_detected(self) -> bool:
|
|
"""
|
|
检查是否检测到语音
|
|
|
|
Returns:
|
|
是否检测到语音
|
|
"""
|
|
return self.recognition_state['is_speech_detected']
|
|
|
|
def get_audio_level(self) -> float:
|
|
"""
|
|
获取当前音频级别
|
|
|
|
Returns:
|
|
音频级别(0.0-1.0)
|
|
"""
|
|
return self.recognition_state['audio_level']
|
|
|
|
def set_recognition_config(self, config: Dict[str, Any]) -> bool:
|
|
"""
|
|
设置识别配置
|
|
|
|
Args:
|
|
config: 配置字典
|
|
|
|
Returns:
|
|
是否设置成功
|
|
"""
|
|
try:
|
|
self.recognition_config.update(config)
|
|
print(f"✓ 识别配置已更新: {self.recognition_config}")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ 识别配置设置失败: {e}")
|
|
return False
|
|
|
|
def get_recognition_config(self) -> Dict[str, Any]:
|
|
"""
|
|
获取识别配置
|
|
|
|
Returns:
|
|
配置字典
|
|
"""
|
|
return self.recognition_config.copy()
|
|
|
|
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 _trigger_recognition_callback(self, callback_type: str, data: Dict[str, Any]):
|
|
"""
|
|
触发识别回调
|
|
|
|
Args:
|
|
callback_type: 回调类型
|
|
data: 回调数据
|
|
"""
|
|
try:
|
|
if callback_type in self.recognition_callbacks:
|
|
for callback in self.recognition_callbacks[callback_type]:
|
|
try:
|
|
callback(data)
|
|
except Exception as e:
|
|
print(f"✗ 识别回调执行失败: {e}")
|
|
except Exception as e:
|
|
print(f"✗ 识别回调触发失败: {e}")
|
|
|
|
def register_recognition_callback(self, callback_type: str, callback: callable):
|
|
"""
|
|
注册识别回调
|
|
|
|
Args:
|
|
callback_type: 回调类型
|
|
callback: 回调函数
|
|
"""
|
|
try:
|
|
if callback_type in self.recognition_callbacks:
|
|
self.recognition_callbacks[callback_type].append(callback)
|
|
print(f"✓ 识别回调已注册: {callback_type}")
|
|
else:
|
|
print(f"✗ 无效的回调类型: {callback_type}")
|
|
except Exception as e:
|
|
print(f"✗ 识别回调注册失败: {e}")
|
|
|
|
def unregister_recognition_callback(self, callback_type: str, callback: callable):
|
|
"""
|
|
注销识别回调
|
|
|
|
Args:
|
|
callback_type: 回调类型
|
|
callback: 回调函数
|
|
"""
|
|
try:
|
|
if callback_type in self.recognition_callbacks:
|
|
if callback in self.recognition_callbacks[callback_type]:
|
|
self.recognition_callbacks[callback_type].remove(callback)
|
|
print(f"✓ 识别回调已注销: {callback_type}")
|
|
except Exception as e:
|
|
print(f"✗ 识别回调注销失败: {e}")
|
|
|
|
def get_stats(self) -> Dict[str, Any]:
|
|
"""
|
|
获取统计信息
|
|
|
|
Returns:
|
|
统计信息字典
|
|
"""
|
|
stats = self.stats.copy()
|
|
if self.stats['successful_recognitions'] > 0:
|
|
stats['average_processing_time'] = (
|
|
self.stats['processing_time_total'] / self.stats['successful_recognitions']
|
|
)
|
|
else:
|
|
stats['average_processing_time'] = 0.0
|
|
return stats
|
|
|
|
def reset_stats(self):
|
|
"""重置统计信息"""
|
|
try:
|
|
self.stats = {
|
|
'audio_chunks_processed': 0,
|
|
'speech_segments_detected': 0,
|
|
'recognition_attempts': 0,
|
|
'successful_recognitions': 0,
|
|
'failed_recognitions': 0,
|
|
'processing_time_total': 0.0
|
|
}
|
|
print("✓ 语音识别器统计信息已重置")
|
|
except Exception as e:
|
|
print(f"✗ 语音识别器统计信息重置失败: {e}") |