VoiceDialogue / main.py
liumaolin
Update `main.py`: modify `uvicorn.run` to use updated `voice_dialogue.api.app` module path.
15e98c9
raw
history blame
10.6 kB
import argparse
import sys
import time
import typing
from pathlib import Path
import multiprocessing
import uvicorn
HERE = Path(__file__).parent
lib_path = HERE / "src"
if lib_path.exists() and lib_path.as_posix() not in sys.path:
sys.path.insert(0, lib_path.as_posix())
from voice_dialogue.core.constants import (
audio_frames_queue,
user_voice_queue,
transcribed_text_queue,
text_input_queue,
audio_output_queue
)
from voice_dialogue.services.audio.capture import EchoCancellingAudioCapture
from voice_dialogue.services.audio.generator import TTSAudioGenerator
from voice_dialogue.services.audio.generators.models import tts_config_registry
from voice_dialogue.services.audio.player import AudioStreamPlayer
from voice_dialogue.services.speech.monitor import SpeechStateMonitor
from voice_dialogue.services.speech.recognizer import ASRWorker
from voice_dialogue.services.text.generator import LLMResponseGenerator
language: typing.Literal['zh', 'en'] = 'en'
def launch_system(
user_language: str,
speaker: str
) -> None:
"""
启动完整的语音对话系统
该函数负责启动并协调语音对话系统的所有组件,包括音频采集、语音识别、
文本生成、语音合成和音频播放等功能模块。系统采用多线程架构,各组件
通过队列进行数据传递和通信。
系统工作流程:
1. 音频采集:EchoCancellingAudioCapture 采集用户语音并进行回声消除
2. 语音监测:SpeechStateMonitor 检测用户是否在说话
3. 语音识别:ASRWorker 将用户语音转换为文本
4. 文本生成:LLMResponseGenerator 基于用户问题生成AI回答
5. 语音合成:TTSAudioGenerator 将AI回答转换为语音
6. 音频播放:AudioStreamPlayer 播放生成的语音
Args:
user_language (str): 用户语言,支持 'zh'(中文)和 'en'(英文)
speaker (str): 语音合成使用的说话人,支持:
'罗翔', '马保国', '沈逸', '杨幂', '周杰伦', '马云'
Raises:
ValueError: 当指定的说话人不在支持列表中时抛出异常
Returns:
None: 函数会一直运行直到所有线程结束
Note:
该函数会阻塞运行,直到系统被外部停止或发生异常
"""
threads = []
#
audio_frame_probe = EchoCancellingAudioCapture(audio_frames_queue=audio_frames_queue)
audio_frame_probe.start()
threads.append(audio_frame_probe)
#
user_voice_checker = SpeechStateMonitor(
audio_frame_queue=audio_frames_queue,
user_voice_queue=user_voice_queue,
)
user_voice_checker.start()
threads.append(user_voice_checker)
#
whisper_worker = ASRWorker(
user_voice_queue=user_voice_queue, transcribed_text_queue=transcribed_text_queue,
language=user_language
)
whisper_worker.start()
threads.append(whisper_worker)
answer_generator_worker = LLMResponseGenerator(
user_question_queue=transcribed_text_queue,
generated_answer_queue=text_input_queue
)
answer_generator_worker.start()
threads.append(answer_generator_worker)
# 动态获取TTS配置,而不是使用固定映射
tts_speaker_config = _get_tts_config_by_speaker_name(speaker)
if tts_speaker_config is None:
# 如果找不到指定说话人,列出所有可用说话人并抛出异常
available_speakers = _get_available_speaker_names()
raise ValueError(f"不支持的TTS说话人: {speaker}。可用说话人: {', '.join(available_speakers)}")
audio_generator_worker = TTSAudioGenerator(
text_input_queue=text_input_queue,
audio_output_queue=audio_output_queue,
tts_config=tts_speaker_config
)
audio_generator_worker.start()
threads.append(audio_generator_worker)
audio_playing_worker = AudioStreamPlayer(audio_playing_queue=audio_output_queue)
audio_playing_worker.start()
threads.append(audio_playing_worker)
while not all([thread.is_ready for thread in threads]):
time.sleep(0.1)
# audio_frame_probe.start_record()
print(f'{"=" * 80}\n服务启动成功\n{"=" * 80}')
for thread in threads:
thread.join()
def _get_tts_config_by_speaker_name(speaker_name: str):
"""
根据说话人名称获取TTS配置
支持中文名称和英文名称,优先匹配中文名称映射,
如果找不到则直接使用英文名称搜索
Args:
speaker_name (str): 说话人名称
Returns:
BaseTTSConfig: TTS配置,如果找不到则返回None
"""
# 中文名称到英文名称的映射(保持向后兼容)
chinese_to_english_mapping = {
'罗翔': 'Luo Xiang',
'马保国': 'Ma Baoguo',
'沈逸': 'Shen Yi',
'杨幂': 'Yang Mi',
'周杰伦': 'Zhou Jielun',
'马云': 'Ma Yun',
}
# 首先尝试中文名称映射
english_name = chinese_to_english_mapping.get(speaker_name, speaker_name)
# 获取所有可用配置
all_configs = tts_config_registry.get_all_configs()
# 搜索匹配的配置
for config in all_configs:
if config.character_name == english_name:
return config
# 如果通过映射找不到,尝试直接匹配输入的名称
if speaker_name != english_name:
for config in all_configs:
if config.character_name == speaker_name:
return config
return None
def _get_available_speaker_names():
"""
获取所有可用的说话人名称列表
Returns:
list[str]: 包含中文显示名称和英文原始名称的列表
"""
# 中文显示名称映射
english_to_chinese_mapping = {
'Luo Xiang': '罗翔',
'Ma Baoguo': '马保国',
'Shen Yi': '沈逸',
'Yang Mi': '杨幂',
'Zhou Jielun': '周杰伦',
'Ma Yun': '马云',
}
all_configs = tts_config_registry.get_all_configs()
speaker_names = []
for config in all_configs:
# 优先显示中文名称
chinese_name = english_to_chinese_mapping.get(config.character_name)
if chinese_name:
speaker_names.append(chinese_name)
else:
# 如果没有中文映射,使用英文原名
speaker_names.append(config.character_name)
return sorted(speaker_names)
def _update_argument_parser_speaker_choices():
"""
动态更新命令行参数解析器中的说话人选项
Returns:
list[str]: 可用的说话人选择列表
"""
return _get_available_speaker_names()
def create_argument_parser():
"""创建命令行参数解析器"""
# 动态获取可用说话人列表
available_speakers = _update_argument_parser_speaker_choices()
parser = argparse.ArgumentParser(
description="VoiceDialogue - 语音对话系统",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=f"""
示例用法:
# 启动命令行模式(默认)
python main.py
# 启动命令行模式并指定参数
python main.py --mode cli --language zh --speaker 沈逸
# 启动API服务器
python main.py --mode api
# 启动API服务器并指定端口
python main.py --mode api --port 9000
# 启动API服务器并启用热重载(开发模式)
python main.py --mode api --port 8000 --reload
支持的说话人:
{', '.join(available_speakers)}
"""
)
# 运行模式选择
parser.add_argument(
'--mode', '-m',
choices=['cli', 'api'],
default='cli',
help='运行模式: cli=命令行模式, api=API服务器模式 (默认: cli)'
)
# 命令行模式参数
cli_group = parser.add_argument_group('命令行模式参数')
cli_group.add_argument(
'--language', '-l',
choices=['zh', 'en'],
default='zh',
help='用户语言: zh=中文, en=英文 (默认: zh)'
)
cli_group.add_argument(
'--speaker', '-s',
choices=available_speakers,
default='沈逸' if '沈逸' in available_speakers else (available_speakers[0] if available_speakers else '沈逸'),
help='TTS说话人 (默认: 沈逸)'
)
# API服务器模式参数
api_group = parser.add_argument_group('API服务器模式参数')
api_group.add_argument(
'--host',
default='0.0.0.0',
help='服务器主机地址 (默认: 0.0.0.0)'
)
api_group.add_argument(
'--port', '-p',
type=int,
default=8000,
help='服务器端口 (默认: 8000)'
)
api_group.add_argument(
'--reload',
action='store_true',
help='启用热重载(开发模式)'
)
return parser
def launch_api_server(host: str = "0.0.0.0", port: int = 8000, reload: bool = False):
"""
启动API服务器
Args:
host (str): 服务器主机地址,默认为 "0.0.0.0"
port (int): 服务器端口,默认为 8000
reload (bool): 是否启用热重载,默认为 False
"""
print(f'{"=" * 80}\n正在启动API服务器...\n{"=" * 80}')
print(f"服务器地址: http://{host}:{port}")
print(f"API文档: http://{host}:{port}/docs")
print(f"热重载: {'启用' if reload else '禁用'}")
print(f'{"=" * 80}')
# 导入并启动FastAPI应用
uvicorn.run(
"voice_dialogue.api.app:app",
host=host,
port=port,
reload=reload,
log_level="info"
)
def main():
"""
主程序入口函数
根据命令行参数选择启动模式:
- cli: 启动命令行语音对话系统
- api: 启动HTTP API服务器
"""
parser = create_argument_parser()
args = parser.parse_args()
print(f"""
{"=" * 80}
VoiceDialogue - 语音对话系统
{"=" * 80}
运行模式: {args.mode.upper()}
{"=" * 80}
""")
try:
if args.mode == 'cli':
print(f"语言设置: {args.language}")
print(f"说话人: {args.speaker}")
print("正在启动命令行语音对话系统...")
launch_system(args.language, args.speaker)
elif args.mode == 'api':
launch_api_server(
host=args.host,
port=args.port,
reload=args.reload
)
except KeyboardInterrupt:
print("\n程序被用户中断")
except Exception as e:
print(f"程序运行出错: {e}")
raise
if __name__ == '__main__':
multiprocessing.freeze_support()
main()