EG/plugins/user/voice_control/synthesis/speech_synthesizer.py
2025-12-12 16:16:15 +08:00

837 lines
27 KiB
Python

"""
语音合成器模块
负责文字转语音功能
"""
import time
import threading
import queue
from typing import Dict, Any, List, Optional
class SpeechSynthesizer:
"""
语音合成器
负责文字转语音功能
"""
def __init__(self, plugin):
"""
初始化语音合成器
Args:
plugin: 语音控制插件实例
"""
self.plugin = plugin
self.enabled = False
self.initialized = False
# 音频输出配置
self.audio_config = {
'sample_rate': 22050,
'channels': 1,
'format': 'int16',
'buffer_size': 4096
}
# 合成配置
self.synthesis_config = {
'language': 'zh-CN',
'voice': 'default',
'speed': 1.0,
'volume': 1.0,
'pitch': 1.0,
'enable_ssml': False,
'output_device': 'default'
}
# 合成队列
self.synthesis_queue = queue.Queue(maxsize=10)
self.playback_queue = queue.Queue(maxsize=5)
# 合成状态
self.synthesis_state = {
'is_synthesizing': False,
'is_playing': False,
'current_text': '',
'progress': 0.0,
'last_synthesis_time': 0.0
}
# 音频输出管理
self.audio_output = None
self.audio_stream = None
# 线程管理
self.synthesis_thread = None
self.playback_thread = None
self.running = False
# 语音库
self.voice_library = {}
# 统计信息
self.stats = {
'texts_synthesized': 0,
'synthesis_attempts': 0,
'successful_syntheses': 0,
'failed_syntheses': 0,
'playback_count': 0,
'synthesis_time_total': 0.0
}
# 回调函数
self.synthesis_callbacks = {
'synthesis_started': [],
'synthesis_completed': [],
'synthesis_error': [],
'playback_started': [],
'playback_completed': [],
'playback_error': []
}
# 时间戳记录
self.last_synthesis_time = 0.0
self.last_playback_time = 0.0
print("✓ 语音合成器已创建")
def initialize(self) -> bool:
"""
初始化语音合成器
Returns:
是否初始化成功
"""
try:
# 初始化音频输出设备
self._initialize_audio_output()
# 初始化语音库
self._initialize_voice_library()
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.synthesis_thread = threading.Thread(target=self._synthesis_loop, daemon=True)
self.synthesis_thread.start()
# 启动播放线程
self.playback_thread = threading.Thread(target=self._playback_loop, daemon=True)
self.playback_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.synthesis_thread and self.synthesis_thread.is_alive():
self.synthesis_thread.join(timeout=2.0)
if self.playback_thread and self.playback_thread.is_alive():
self.playback_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.synthesis_callbacks.clear()
# 清理队列
while not self.synthesis_queue.empty():
try:
self.synthesis_queue.get_nowait()
except queue.Empty:
break
while not self.playback_queue.empty():
try:
self.playback_queue.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_synthesis_time = current_time
except Exception as e:
print(f"✗ 语音合成器更新失败: {e}")
import traceback
traceback.print_exc()
def _initialize_audio_output(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 _initialize_voice_library(self):
"""初始化语音库"""
try:
# 创建模拟语音库
self.voice_library = {
'default': {
'name': '默认语音',
'language': 'zh-CN',
'gender': 'neutral',
'description': '系统默认语音'
},
'male': {
'name': '男声',
'language': 'zh-CN',
'gender': 'male',
'description': '标准男声'
},
'female': {
'name': '女声',
'language': 'zh-CN',
'gender': 'female',
'description': '标准女声'
},
'child': {
'name': '童声',
'language': 'zh-CN',
'gender': 'child',
'description': '儿童语音'
}
}
print("✓ 语音库初始化完成")
print(f" 可用语音: {list(self.voice_library.keys())}")
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 _synthesis_loop(self):
"""合成循环线程"""
try:
# 启动音频流
if not self._start_audio_stream():
return
while self.running and self.enabled:
try:
# 从队列获取合成任务
if not self.synthesis_queue.empty():
synthesis_task = self.synthesis_queue.get_nowait()
self._process_synthesis_task(synthesis_task)
# 控制处理频率
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 _playback_loop(self):
"""播放循环线程"""
try:
while self.running and self.enabled:
try:
# 从队列获取播放任务
if not self.playback_queue.empty():
playback_task = self.playback_queue.get_nowait()
self._process_playback_task(playback_task)
# 控制播放频率
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 _process_synthesis_task(self, task: Dict[str, Any]):
"""
处理合成任务
Args:
task: 合成任务字典
"""
try:
start_time = time.time()
text = task.get('text', '')
speed = task.get('speed', 1.0)
volume = task.get('volume', 1.0)
blocking = task.get('blocking', False)
# 更新合成状态
self.synthesis_state['is_synthesizing'] = True
self.synthesis_state['current_text'] = text
self.synthesis_state['last_synthesis_time'] = start_time
# 触发合成开始回调
self._trigger_synthesis_callback('synthesis_started', {
'text': text,
'speed': speed,
'volume': volume
})
# 模拟语音合成过程
synthesized_audio = self._synthesize_text(text, speed, volume)
if synthesized_audio is not None:
# 添加到播放队列
try:
self.playback_queue.put_nowait({
'audio': synthesized_audio,
'text': text,
'blocking': blocking,
'timestamp': time.time()
})
# 更新统计信息
self.stats['successful_syntheses'] += 1
# 触发合成完成回调
self._trigger_synthesis_callback('synthesis_completed', {
'text': text,
'audio_length': len(synthesized_audio),
'synthesis_time': time.time() - start_time
})
except queue.Full:
print("✗ 播放队列已满,丢弃合成结果")
self.stats['failed_syntheses'] += 1
# 触发合成错误回调
self._trigger_synthesis_callback('synthesis_error', {
'text': text,
'error': 'Playback queue full'
})
else:
self.stats['failed_syntheses'] += 1
# 触发合成错误回调
self._trigger_synthesis_callback('synthesis_error', {
'text': text,
'error': 'Synthesis failed'
})
# 更新统计信息
synthesis_time = time.time() - start_time
self.stats['synthesis_time_total'] += synthesis_time
self.stats['synthesis_attempts'] += 1
self.last_synthesis_time = time.time()
except Exception as e:
print(f"✗ 合成任务处理失败: {e}")
self.stats['failed_syntheses'] += 1
finally:
# 重置合成状态
self.synthesis_state['is_synthesizing'] = False
self.synthesis_state['progress'] = 0.0
def _process_playback_task(self, task: Dict[str, Any]):
"""
处理播放任务
Args:
task: 播放任务字典
"""
try:
audio_data = task.get('audio', b'')
text = task.get('text', '')
blocking = task.get('blocking', False)
# 更新播放状态
self.synthesis_state['is_playing'] = True
self.synthesis_state['current_text'] = text
# 触发播放开始回调
self._trigger_synthesis_callback('playback_started', {
'text': text,
'audio_length': len(audio_data)
})
# 模拟音频播放
playback_success = self._play_audio(audio_data, blocking)
if playback_success:
self.stats['playback_count'] += 1
# 触发播放完成回调
self._trigger_synthesis_callback('playback_completed', {
'text': text,
'playback_time': len(audio_data) / self.audio_config['sample_rate']
})
else:
# 触发播放错误回调
self._trigger_synthesis_callback('playback_error', {
'text': text,
'error': 'Playback failed'
})
self.last_playback_time = time.time()
except Exception as e:
print(f"✗ 播放任务处理失败: {e}")
# 触发播放错误回调
self._trigger_synthesis_callback('playback_error', {
'text': task.get('text', ''),
'error': str(e)
})
finally:
# 重置播放状态
self.synthesis_state['is_playing'] = False
def _synthesize_text(self, text: str, speed: float, volume: float) -> Optional[bytes]:
"""
合成文本为音频
Args:
text: 要合成的文本
speed: 语速
volume: 音量
Returns:
音频数据或None
"""
try:
# 模拟文本到语音的转换过程
import random
import struct
# 根据文本长度计算音频长度
char_count = len(text)
# 假设每个字符大约需要0.1秒
duration_seconds = char_count * 0.1 * (1.0 / speed)
# 计算样本数
sample_rate = self.audio_config['sample_rate']
total_samples = int(duration_seconds * sample_rate)
# 生成模拟的音频数据
audio_data = bytearray()
# 生成简单的正弦波作为语音模拟
frequency = 440.0 # A音
for i in range(total_samples):
# 添加一些变化使声音更自然
t = i / sample_rate
wave_value = math.sin(2 * math.pi * frequency * t)
# 添加谐波
wave_value += 0.3 * math.sin(2 * math.pi * frequency * 2 * t)
wave_value += 0.2 * math.sin(2 * math.pi * frequency * 3 * t)
# 应用音量
wave_value *= volume
# 转换为16位整数
sample = int(wave_value * 32767 * 0.3) # 降低整体音量
audio_data.extend(struct.pack('<h', sample))
return bytes(audio_data)
except Exception as e:
print(f"✗ 文本合成失败: {e}")
return None
def _play_audio(self, audio_data: bytes, blocking: bool = False) -> bool:
"""
播放音频数据
Args:
audio_data: 音频数据
blocking: 是否阻塞等待播放完成
Returns:
是否播放成功
"""
try:
# 模拟音频播放
import time
import math
# 计算播放时间
sample_count = len(audio_data) // 2 # 16位音频
duration = sample_count / self.audio_config['sample_rate']
print(f"✓ 播放音频: {duration:.2f}")
# 如果是阻塞模式,等待播放完成
if blocking:
time.sleep(duration)
return True
except Exception as e:
print(f"✗ 音频播放失败: {e}")
return False
def synthesize_speech(self, text: str, blocking: bool = False,
speed: float = 1.0, volume: float = 1.0) -> bool:
"""
合成并播放语音
Args:
text: 要合成的文本
blocking: 是否阻塞等待播放完成
speed: 语速(0.5-2.0)
volume: 音量(0.0-1.0)
Returns:
是否合成成功
"""
try:
if not self.enabled:
print("✗ 语音合成器未启用")
return False
# 限制参数范围
speed = max(0.5, min(2.0, speed))
volume = max(0.0, min(1.0, volume))
# 创建合成任务
synthesis_task = {
'text': text,
'speed': speed,
'volume': volume,
'blocking': blocking,
'timestamp': time.time()
}
# 添加到合成队列
try:
self.synthesis_queue.put_nowait(synthesis_task)
self.stats['texts_synthesized'] += 1
return True
except queue.Full:
print("✗ 合成队列已满")
return False
except Exception as e:
print(f"✗ 语音合成失败: {e}")
self.stats['failed_syntheses'] += 1
return False
def stop_speech(self) -> bool:
"""
停止当前语音播放
Returns:
是否停止成功
"""
try:
# 清空播放队列
while not self.playback_queue.empty():
try:
self.playback_queue.get_nowait()
except queue.Empty:
break
# 重置播放状态
self.synthesis_state['is_playing'] = False
self.synthesis_state['current_text'] = ''
print("✓ 语音播放已停止")
return True
except Exception as e:
print(f"✗ 语音播放停止失败: {e}")
return False
def get_available_voices(self) -> Dict[str, Dict[str, Any]]:
"""
获取可用语音列表
Returns:
语音信息字典
"""
return self.voice_library.copy()
def set_voice(self, voice_name: str) -> bool:
"""
设置语音
Args:
voice_name: 语音名称
Returns:
是否设置成功
"""
try:
if voice_name in self.voice_library:
self.synthesis_config['voice'] = voice_name
print(f"✓ 语音已设置为: {voice_name}")
return True
else:
print(f"✗ 语音不存在: {voice_name}")
return False
except Exception as e:
print(f"✗ 语音设置失败: {e}")
return False
def set_synthesis_config(self, config: Dict[str, Any]) -> bool:
"""
设置合成配置
Args:
config: 配置字典
Returns:
是否设置成功
"""
try:
self.synthesis_config.update(config)
print(f"✓ 合成配置已更新: {self.synthesis_config}")
return True
except Exception as e:
print(f"✗ 合成配置设置失败: {e}")
return False
def get_synthesis_config(self) -> Dict[str, Any]:
"""
获取合成配置
Returns:
配置字典
"""
return self.synthesis_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 is_speaking(self) -> bool:
"""
检查是否正在播放语音
Returns:
是否正在播放语音
"""
return self.synthesis_state['is_playing']
def is_synthesizing(self) -> bool:
"""
检查是否正在合成语音
Returns:
是否正在合成语音
"""
return self.synthesis_state['is_synthesizing']
def get_synthesis_progress(self) -> float:
"""
获取合成进度
Returns:
合成进度(0.0-1.0)
"""
return self.synthesis_state['progress']
def _trigger_synthesis_callback(self, callback_type: str, data: Dict[str, Any]):
"""
触发合成回调
Args:
callback_type: 回调类型
data: 回调数据
"""
try:
if callback_type in self.synthesis_callbacks:
for callback in self.synthesis_callbacks[callback_type]:
try:
callback(data)
except Exception as e:
print(f"✗ 合成回调执行失败: {e}")
except Exception as e:
print(f"✗ 合成回调触发失败: {e}")
def register_synthesis_callback(self, callback_type: str, callback: callable):
"""
注册合成回调
Args:
callback_type: 回调类型
callback: 回调函数
"""
try:
if callback_type in self.synthesis_callbacks:
self.synthesis_callbacks[callback_type].append(callback)
print(f"✓ 合成回调已注册: {callback_type}")
else:
print(f"✗ 无效的回调类型: {callback_type}")
except Exception as e:
print(f"✗ 合成回调注册失败: {e}")
def unregister_synthesis_callback(self, callback_type: str, callback: callable):
"""
注销合成回调
Args:
callback_type: 回调类型
callback: 回调函数
"""
try:
if callback_type in self.synthesis_callbacks:
if callback in self.synthesis_callbacks[callback_type]:
self.synthesis_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_syntheses'] > 0:
stats['average_synthesis_time'] = (
self.stats['synthesis_time_total'] / self.stats['successful_syntheses']
)
else:
stats['average_synthesis_time'] = 0.0
return stats
def reset_stats(self):
"""重置统计信息"""
try:
self.stats = {
'texts_synthesized': 0,
'synthesis_attempts': 0,
'successful_syntheses': 0,
'failed_syntheses': 0,
'playback_count': 0,
'synthesis_time_total': 0.0
}
print("✓ 语音合成器统计信息已重置")
except Exception as e:
print(f"✗ 语音合成器统计信息重置失败: {e}")
# 导入math模块用于正弦波计算
import math