774 lines
27 KiB
Python
774 lines
27 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
多进程音频控制系统
|
||
实现主控制进程和状态管理
|
||
"""
|
||
|
||
import multiprocessing as mp
|
||
import queue
|
||
import time
|
||
import threading
|
||
import requests
|
||
import json
|
||
import base64
|
||
import gzip
|
||
import uuid
|
||
import asyncio
|
||
import websockets
|
||
from typing import Optional, Dict, Any, List
|
||
from dataclasses import dataclass, asdict
|
||
from enum import Enum
|
||
import os
|
||
import sys
|
||
|
||
from audio_processes import (
|
||
InputProcess, OutputProcess,
|
||
RecordingState, ControlCommand, ProcessEvent
|
||
)
|
||
|
||
class ControlSystem:
|
||
"""主控制系统"""
|
||
|
||
def __init__(self, config: Dict[str, Any] = None):
|
||
self.config = config or self._get_default_config()
|
||
|
||
# 进程间通信
|
||
self.input_command_queue = mp.Queue(maxsize=100) # 主进程 → 输入进程
|
||
self.input_event_queue = mp.Queue(maxsize=100) # 输入进程 → 主进程
|
||
self.output_audio_queue = mp.Queue(maxsize=1000) # 主进程 → 输出进程
|
||
|
||
# 进程
|
||
self.input_process = None
|
||
self.output_process = None
|
||
|
||
# 状态管理
|
||
self.state = RecordingState.IDLE
|
||
self.processing_complete = False
|
||
self.playback_complete = False
|
||
|
||
# 当前处理的数据
|
||
self.current_audio_data = None
|
||
self.current_audio_metadata = None
|
||
|
||
# API配置
|
||
self.api_config = self._setup_api_config()
|
||
|
||
# 统计信息
|
||
self.stats = {
|
||
'total_conversations': 0,
|
||
'total_recording_time': 0,
|
||
'successful_processing': 0,
|
||
'failed_processing': 0
|
||
}
|
||
|
||
# 运行状态
|
||
self.running = True
|
||
|
||
# 检查依赖
|
||
self._check_dependencies()
|
||
|
||
def _get_default_config(self) -> Dict[str, Any]:
|
||
"""获取默认配置"""
|
||
return {
|
||
'system': {
|
||
'max_queue_size': 1000,
|
||
'process_timeout': 30,
|
||
'heartbeat_interval': 1.0
|
||
},
|
||
'audio': {
|
||
'sample_rate': 16000,
|
||
'channels': 1,
|
||
'chunk_size': 1024
|
||
},
|
||
'recording': {
|
||
'min_duration': 2.0,
|
||
'max_duration': 30.0,
|
||
'silence_threshold': 3.0
|
||
},
|
||
'processing': {
|
||
'enable_asr': True,
|
||
'enable_llm': True,
|
||
'enable_tts': True,
|
||
'character': 'libai'
|
||
}
|
||
}
|
||
|
||
def _setup_api_config(self) -> Dict[str, Any]:
|
||
"""设置API配置"""
|
||
config = {
|
||
'asr': {
|
||
'appid': "8718217928",
|
||
'token': "ynJMX-5ix1FsJvswC9KTNlGUdubcchqc",
|
||
'cluster': "volcengine_input_common",
|
||
'ws_url': "wss://openspeech.bytedance.com/api/v2/asr"
|
||
},
|
||
'llm': {
|
||
'api_url': "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
|
||
'model': "doubao-seed-1-6-flash-250828",
|
||
'api_key': os.environ.get("ARK_API_KEY", ""),
|
||
'max_tokens': 50
|
||
},
|
||
'tts': {
|
||
'url': "https://openspeech.bytedance.com/api/v3/tts/unidirectional",
|
||
'app_id': "8718217928",
|
||
'access_key': "ynJMX-5ix1FsJvswC9KTNlGUdubcchqc",
|
||
'resource_id': "volc.service_type.10029",
|
||
'app_key': "aGjiRDfUWi",
|
||
'speaker': "zh_female_wanqudashu_moon_bigtts"
|
||
}
|
||
}
|
||
|
||
# 加载角色配置
|
||
character_config = self._load_character_config(self.config['processing']['character'])
|
||
if character_config and "voice" in character_config:
|
||
config['tts']['speaker'] = character_config["voice"]
|
||
|
||
return config
|
||
|
||
def _load_character_config(self, character_name: str) -> Optional[Dict[str, Any]]:
|
||
"""加载角色配置"""
|
||
characters_dir = os.path.join(os.path.dirname(__file__), "characters")
|
||
config_file = os.path.join(characters_dir, f"{character_name}.json")
|
||
|
||
if not os.path.exists(config_file):
|
||
print(f"⚠️ 角色配置文件不存在: {config_file}")
|
||
return None
|
||
|
||
try:
|
||
with open(config_file, 'r', encoding='utf-8') as f:
|
||
config = json.load(f)
|
||
|
||
print(f"✅ 加载角色: {config.get('name', character_name)}")
|
||
return config
|
||
|
||
except Exception as e:
|
||
print(f"❌ 加载角色配置失败: {e}")
|
||
return None
|
||
|
||
def _check_dependencies(self):
|
||
"""检查依赖库"""
|
||
missing_deps = []
|
||
|
||
try:
|
||
import pyaudio
|
||
except ImportError:
|
||
missing_deps.append("pyaudio")
|
||
|
||
try:
|
||
import numpy
|
||
except ImportError:
|
||
missing_deps.append("numpy")
|
||
|
||
try:
|
||
import requests
|
||
except ImportError:
|
||
missing_deps.append("requests")
|
||
|
||
try:
|
||
import websockets
|
||
except ImportError:
|
||
missing_deps.append("websockets")
|
||
|
||
if missing_deps:
|
||
print(f"❌ 缺少依赖库: {', '.join(missing_deps)}")
|
||
print("请安装: pip install " + " ".join(missing_deps))
|
||
sys.exit(1)
|
||
|
||
# 检查API密钥
|
||
if not self.api_config['llm']['api_key']:
|
||
print("⚠️ 未设置 ARK_API_KEY 环境变量,大语言模型功能将被禁用")
|
||
self.config['processing']['enable_llm'] = False
|
||
|
||
def start(self):
|
||
"""启动系统"""
|
||
print("🚀 启动多进程音频控制系统")
|
||
print("=" * 60)
|
||
|
||
# 创建并启动输入进程
|
||
input_config = {
|
||
'zcr_min': 2400,
|
||
'zcr_max': 12000,
|
||
'min_recording_time': self.config['recording']['min_duration'],
|
||
'max_recording_time': self.config['recording']['max_duration'],
|
||
'silence_threshold': self.config['recording']['silence_threshold'],
|
||
'pre_record_duration': 2.0
|
||
}
|
||
|
||
self.input_process = mp.Process(
|
||
target=InputProcess(
|
||
self.input_command_queue,
|
||
self.input_event_queue,
|
||
input_config
|
||
).run
|
||
)
|
||
|
||
# 创建并启动输出进程
|
||
output_config = {
|
||
'buffer_size': 1000,
|
||
'show_progress': True,
|
||
'progress_interval': 100
|
||
}
|
||
|
||
self.output_process = mp.Process(
|
||
target=OutputProcess(
|
||
self.output_audio_queue,
|
||
output_config
|
||
).run
|
||
)
|
||
|
||
# 启动进程
|
||
self.input_process.start()
|
||
self.output_process.start()
|
||
|
||
print("✅ 所有进程已启动")
|
||
print("🎙️ 输入进程:负责录音和语音检测")
|
||
print("🔊 输出进程:负责音频播放")
|
||
print("🎯 主控制:负责协调和AI处理")
|
||
print("=" * 60)
|
||
|
||
# 启动主控制循环
|
||
self._control_loop()
|
||
|
||
def _control_loop(self):
|
||
"""主控制循环"""
|
||
print("🎯 主控制循环启动")
|
||
|
||
try:
|
||
while self.running:
|
||
# 根据状态处理不同逻辑
|
||
if self.state == RecordingState.IDLE:
|
||
self._handle_idle_state()
|
||
|
||
elif self.state == RecordingState.RECORDING:
|
||
self._handle_recording_state()
|
||
|
||
elif self.state == RecordingState.PROCESSING:
|
||
self._handle_processing_state()
|
||
|
||
elif self.state == RecordingState.PLAYING:
|
||
self._handle_playing_state()
|
||
|
||
# 检查进程事件
|
||
self._check_events()
|
||
|
||
# 显示状态
|
||
self._display_status()
|
||
|
||
# 控制循环频率
|
||
time.sleep(0.1)
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n👋 收到退出信号...")
|
||
self.shutdown()
|
||
except Exception as e:
|
||
print(f"❌ 主控制循环错误: {e}")
|
||
self.shutdown()
|
||
|
||
def _handle_idle_state(self):
|
||
"""处理空闲状态"""
|
||
if self.state == RecordingState.IDLE:
|
||
# 启用输入进程录音功能
|
||
self.input_command_queue.put(ControlCommand('enable_recording'))
|
||
self.state = RecordingState.RECORDING
|
||
print("🎯 状态:IDLE → RECORDING")
|
||
|
||
def _handle_recording_state(self):
|
||
"""处理录音状态"""
|
||
# 等待输入进程发送录音完成事件
|
||
pass
|
||
|
||
def _handle_processing_state(self):
|
||
"""处理状态"""
|
||
if not self.processing_complete:
|
||
self._process_audio_pipeline()
|
||
|
||
def _handle_playing_state(self):
|
||
"""处理播放状态"""
|
||
# 检查播放是否完成
|
||
if self.output_audio_queue.qsize() == 0 and not self.playback_complete:
|
||
# 等待一小段时间确保播放完成
|
||
time.sleep(0.5)
|
||
if self.output_audio_queue.qsize() == 0:
|
||
self.playback_complete = True
|
||
self.stats['total_conversations'] += 1
|
||
|
||
def _check_events(self):
|
||
"""检查进程事件"""
|
||
# 检查输入进程事件
|
||
try:
|
||
while True:
|
||
event = self.input_event_queue.get_nowait()
|
||
|
||
if event.event_type == 'recording_complete':
|
||
print("📡 主控制:收到录音完成事件")
|
||
self._handle_recording_complete(event)
|
||
|
||
except queue.Empty:
|
||
pass
|
||
|
||
def _handle_recording_complete(self, event: ProcessEvent):
|
||
"""处理录音完成事件"""
|
||
# 禁用输入进程录音功能
|
||
self.input_command_queue.put(ControlCommand('disable_recording'))
|
||
|
||
# 保存录音数据
|
||
self.current_audio_data = event.data
|
||
self.current_audio_metadata = event.metadata
|
||
|
||
# 更新统计
|
||
self.stats['total_recording_time'] += event.metadata['duration']
|
||
|
||
# 切换到处理状态
|
||
self.state = RecordingState.PROCESSING
|
||
self.processing_complete = False
|
||
self.playback_complete = False
|
||
|
||
print(f"🎯 状态:RECORDING → PROCESSING (时长: {event.metadata['duration']:.2f}s)")
|
||
|
||
def _process_audio_pipeline(self):
|
||
"""处理音频流水线:STT + LLM + TTS"""
|
||
try:
|
||
print("🤖 开始处理音频流水线")
|
||
|
||
# 1. 语音识别 (STT)
|
||
if self.config['processing']['enable_asr']:
|
||
text = self._speech_to_text(self.current_audio_data)
|
||
if not text:
|
||
print("❌ 语音识别失败")
|
||
self._handle_processing_failure()
|
||
return
|
||
|
||
print(f"📝 识别结果: {text}")
|
||
else:
|
||
text = "语音识别功能已禁用"
|
||
|
||
# 2. 大语言模型 (LLM)
|
||
if self.config['processing']['enable_llm']:
|
||
response = self._call_llm(text)
|
||
if not response:
|
||
print("❌ 大语言模型调用失败")
|
||
self._handle_processing_failure()
|
||
return
|
||
|
||
print(f"💬 AI回复: {response}")
|
||
else:
|
||
response = "大语言模型功能已禁用"
|
||
|
||
# 3. 文本转语音 (TTS)
|
||
if self.config['processing']['enable_tts']:
|
||
success = self._text_to_speech_streaming(response)
|
||
if not success:
|
||
print("❌ 文本转语音失败")
|
||
self._handle_processing_failure()
|
||
return
|
||
else:
|
||
print("ℹ️ 文本转语音功能已禁用")
|
||
# 直接发送结束信号
|
||
self.output_audio_queue.put(None)
|
||
|
||
# 标记处理完成
|
||
self.processing_complete = True
|
||
self.state = RecordingState.PLAYING
|
||
self.stats['successful_processing'] += 1
|
||
|
||
print("🎯 状态:PROCESSING → PLAYING")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 处理流水线错误: {e}")
|
||
self._handle_processing_failure()
|
||
|
||
def _handle_processing_failure(self):
|
||
"""处理失败情况"""
|
||
self.stats['failed_processing'] += 1
|
||
self.state = RecordingState.IDLE
|
||
self.processing_complete = True
|
||
self.playback_complete = True
|
||
print("🎯 状态:PROCESSING → IDLE (失败)")
|
||
|
||
def _speech_to_text(self, audio_data: bytes) -> Optional[str]:
|
||
"""语音转文字"""
|
||
try:
|
||
return asyncio.run(self._recognize_audio_async(audio_data))
|
||
except Exception as e:
|
||
print(f"❌ 语音识别异常: {e}")
|
||
return None
|
||
|
||
async def _recognize_audio_async(self, audio_data: bytes) -> Optional[str]:
|
||
"""异步语音识别"""
|
||
if not self.config['processing']['enable_asr']:
|
||
return "语音识别功能已禁用"
|
||
|
||
try:
|
||
import websockets
|
||
|
||
# 生成ASR头部
|
||
def generate_asr_header(message_type=1, message_type_specific_flags=0):
|
||
PROTOCOL_VERSION = 0b0001
|
||
DEFAULT_HEADER_SIZE = 0b0001
|
||
JSON = 0b0001
|
||
GZIP = 0b0001
|
||
|
||
header = bytearray()
|
||
header.append((PROTOCOL_VERSION << 4) | DEFAULT_HEADER_SIZE)
|
||
header.append((message_type << 4) | message_type_specific_flags)
|
||
header.append((JSON << 4) | GZIP)
|
||
header.append(0x00) # reserved
|
||
return header
|
||
|
||
# 解析ASR响应
|
||
def parse_asr_response(res):
|
||
# 简化的响应解析
|
||
if len(res) < 8:
|
||
return {}
|
||
|
||
message_type = res[1] >> 4
|
||
payload_size = int.from_bytes(res[4:8], "big", signed=False)
|
||
payload_msg = res[8:8+payload_size]
|
||
|
||
if message_type == 0b1001: # SERVER_FULL_RESPONSE
|
||
try:
|
||
if payload_msg.startswith(b'{'):
|
||
result = json.loads(payload_msg.decode('utf-8'))
|
||
return result
|
||
except:
|
||
pass
|
||
|
||
return {}
|
||
|
||
# 构建请求参数
|
||
reqid = str(uuid.uuid4())
|
||
request_params = {
|
||
'app': {
|
||
'appid': self.api_config['asr']['appid'],
|
||
'cluster': self.api_config['asr']['cluster'],
|
||
'token': self.api_config['asr']['token'],
|
||
},
|
||
'user': {
|
||
'uid': 'multiprocess_asr'
|
||
},
|
||
'request': {
|
||
'reqid': reqid,
|
||
'nbest': 1,
|
||
'workflow': 'audio_in,resample,partition,vad,fe,decode,itn,nlu_punctuate',
|
||
'show_language': False,
|
||
'show_utterances': False,
|
||
'result_type': 'full',
|
||
"sequence": 1
|
||
},
|
||
'audio': {
|
||
'format': 'wav',
|
||
'rate': self.config['audio']['sample_rate'],
|
||
'language': 'zh-CN',
|
||
'bits': 16,
|
||
'channel': self.config['audio']['channels'],
|
||
'codec': 'raw'
|
||
}
|
||
}
|
||
|
||
# 构建请求
|
||
payload_bytes = str.encode(json.dumps(request_params))
|
||
payload_bytes = gzip.compress(payload_bytes)
|
||
full_client_request = bytearray(generate_asr_header())
|
||
full_client_request.extend((len(payload_bytes)).to_bytes(4, 'big'))
|
||
full_client_request.extend(payload_bytes)
|
||
|
||
# 设置认证头
|
||
additional_headers = {'Authorization': 'Bearer; {}'.format(self.api_config['asr']['token'])}
|
||
|
||
# 连接WebSocket
|
||
async with websockets.connect(
|
||
self.api_config['asr']['ws_url'],
|
||
additional_headers=additional_headers,
|
||
max_size=1000000000
|
||
) as ws:
|
||
# 发送请求
|
||
await ws.send(full_client_request)
|
||
res = await ws.recv()
|
||
result = parse_asr_response(res)
|
||
|
||
# 发送音频数据
|
||
chunk_size = int(self.config['audio']['channels'] * 2 *
|
||
self.config['audio']['sample_rate'] * 15000 / 1000)
|
||
|
||
for offset in range(0, len(audio_data), chunk_size):
|
||
chunk = audio_data[offset:offset + chunk_size]
|
||
last = (offset + chunk_size) >= len(audio_data)
|
||
|
||
payload_bytes = gzip.compress(chunk)
|
||
audio_only_request = bytearray(
|
||
generate_asr_header(
|
||
message_type=0b0010,
|
||
message_type_specific_flags=0b0010 if last else 0
|
||
)
|
||
)
|
||
audio_only_request.extend((len(payload_bytes)).to_bytes(4, 'big'))
|
||
audio_only_request.extend(payload_bytes)
|
||
|
||
await ws.send(audio_only_request)
|
||
res = await ws.recv()
|
||
result = parse_asr_response(res)
|
||
|
||
# 获取最终结果
|
||
if 'payload_msg' in result and 'result' in result['payload_msg']:
|
||
results = result['payload_msg']['result']
|
||
if results:
|
||
return results[0].get('text', '识别失败')
|
||
|
||
return None
|
||
|
||
except Exception as e:
|
||
print(f"❌ 语音识别失败: {e}")
|
||
return None
|
||
|
||
def _call_llm(self, text: str) -> Optional[str]:
|
||
"""调用大语言模型"""
|
||
if not self.config['processing']['enable_llm']:
|
||
return "大语言模型功能已禁用"
|
||
|
||
try:
|
||
# 获取角色配置
|
||
character_config = self._load_character_config(self.config['processing']['character'])
|
||
if character_config and "system_prompt" in character_config:
|
||
system_prompt = character_config["system_prompt"]
|
||
else:
|
||
system_prompt = "你是一个智能助手,请根据用户的语音输入提供有帮助的回答。保持回答简洁明了。"
|
||
|
||
# 构建请求
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {self.api_config['llm']['api_key']}"
|
||
}
|
||
|
||
messages = [
|
||
{"role": "system", "content": system_prompt},
|
||
{"role": "user", "content": text}
|
||
]
|
||
|
||
data = {
|
||
"model": self.api_config['llm']['model'],
|
||
"messages": messages,
|
||
"max_tokens": self.api_config['llm']['max_tokens'],
|
||
"stream": False # 非流式,简化实现
|
||
}
|
||
|
||
response = requests.post(
|
||
self.api_config['llm']['api_url'],
|
||
headers=headers,
|
||
json=data,
|
||
timeout=30
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
result = response.json()
|
||
if 'choices' in result and len(result['choices']) > 0:
|
||
content = result['choices'][0]['message']['content']
|
||
return content.strip()
|
||
|
||
print(f"❌ LLM API调用失败: {response.status_code}")
|
||
return None
|
||
|
||
except Exception as e:
|
||
print(f"❌ 大语言模型调用失败: {e}")
|
||
return None
|
||
|
||
def _text_to_speech_streaming(self, text: str) -> bool:
|
||
"""文本转语音(流式)"""
|
||
if not self.config['processing']['enable_tts']:
|
||
return False
|
||
|
||
try:
|
||
print("🎵 开始文本转语音")
|
||
|
||
# 发送元数据
|
||
self.output_audio_queue.put(f"METADATA:{text[:30]}...")
|
||
|
||
# 构建请求头
|
||
headers = {
|
||
"X-Api-App-Id": self.api_config['tts']['app_id'],
|
||
"X-Api-Access-Key": self.api_config['tts']['access_key'],
|
||
"X-Api-Resource-Id": self.api_config['tts']['resource_id'],
|
||
"X-Api-App-Key": self.api_config['tts']['app_key'],
|
||
"Content-Type": "application/json",
|
||
"Connection": "keep-alive"
|
||
}
|
||
|
||
# 构建请求参数
|
||
payload = {
|
||
"user": {
|
||
"uid": "multiprocess_tts"
|
||
},
|
||
"req_params": {
|
||
"text": text,
|
||
"speaker": self.api_config['tts']['speaker'],
|
||
"audio_params": {
|
||
"format": "pcm",
|
||
"sample_rate": self.config['audio']['sample_rate'],
|
||
"enable_timestamp": True
|
||
},
|
||
"additions": "{\"explicit_language\":\"zh\",\"disable_markdown_filter\":true, \"enable_timestamp\":true}\"}"
|
||
}
|
||
}
|
||
|
||
# 发送请求
|
||
session = requests.Session()
|
||
try:
|
||
response = session.post(
|
||
self.api_config['tts']['url'],
|
||
headers=headers,
|
||
json=payload,
|
||
stream=True
|
||
)
|
||
|
||
if response.status_code != 200:
|
||
print(f"❌ TTS请求失败: {response.status_code}")
|
||
return False
|
||
|
||
# 处理流式响应
|
||
total_audio_size = 0
|
||
chunk_count = 0
|
||
|
||
for chunk in response.iter_lines(decode_unicode=True):
|
||
if not chunk:
|
||
continue
|
||
|
||
try:
|
||
data = json.loads(chunk)
|
||
|
||
if data.get("code", 0) == 0 and "data" in data and data["data"]:
|
||
chunk_audio = base64.b64decode(data["data"])
|
||
audio_size = len(chunk_audio)
|
||
total_audio_size += audio_size
|
||
chunk_count += 1
|
||
|
||
# 发送到输出进程
|
||
self.output_audio_queue.put(chunk_audio)
|
||
|
||
# 显示进度
|
||
if chunk_count % 10 == 0:
|
||
progress = f"📥 TTS生成: {chunk_count} 块 | {total_audio_size / 1024:.1f} KB"
|
||
print(f"\r{progress}", end='', flush=True)
|
||
|
||
if data.get("code", 0) == 20000000:
|
||
break
|
||
|
||
except json.JSONDecodeError:
|
||
continue
|
||
|
||
print(f"\n✅ TTS音频生成完成: {chunk_count} 块, {total_audio_size / 1024:.1f} KB")
|
||
|
||
# 发送结束信号
|
||
self.output_audio_queue.put(None)
|
||
|
||
return chunk_count > 0
|
||
|
||
finally:
|
||
response.close()
|
||
session.close()
|
||
|
||
except Exception as e:
|
||
print(f"❌ 文本转语音失败: {e}")
|
||
return False
|
||
|
||
def _display_status(self):
|
||
"""显示系统状态"""
|
||
# 每秒显示一次状态
|
||
if hasattr(self, '_last_status_time'):
|
||
if time.time() - self._last_status_time < 1.0:
|
||
return
|
||
|
||
self._last_status_time = time.time()
|
||
|
||
# 状态显示
|
||
status_lines = [
|
||
f"🎯 状态: {self.state.value}",
|
||
f"📊 统计: 对话{self.stats['total_conversations']} | "
|
||
f"录音{self.stats['total_recording_time']:.1f}s | "
|
||
f"成功{self.stats['successful_processing']} | "
|
||
f"失败{self.stats['failed_processing']}"
|
||
]
|
||
|
||
# 队列状态
|
||
input_queue_size = self.input_command_queue.qsize()
|
||
output_queue_size = self.output_audio_queue.qsize()
|
||
|
||
if input_queue_size > 0 or output_queue_size > 0:
|
||
status_lines.append(f"📦 队列: 输入{input_queue_size} | 输出{output_queue_size}")
|
||
|
||
# 显示状态
|
||
status_str = " | ".join(status_lines)
|
||
print(f"\r{status_str}", end='', flush=True)
|
||
|
||
def shutdown(self):
|
||
"""关闭系统"""
|
||
print("\n🛑 正在关闭系统...")
|
||
|
||
self.running = False
|
||
|
||
# 发送关闭命令
|
||
try:
|
||
self.input_command_queue.put(ControlCommand('shutdown'))
|
||
self.output_audio_queue.put(None)
|
||
except:
|
||
pass
|
||
|
||
# 等待进程结束
|
||
if self.input_process:
|
||
try:
|
||
self.input_process.join(timeout=5)
|
||
except:
|
||
pass
|
||
|
||
if self.output_process:
|
||
try:
|
||
self.output_process.join(timeout=5)
|
||
except:
|
||
pass
|
||
|
||
# 显示最终统计
|
||
print("\n📊 最终统计:")
|
||
print(f" 总对话次数: {self.stats['total_conversations']}")
|
||
print(f" 总录音时长: {self.stats['total_recording_time']:.1f} 秒")
|
||
print(f" 成功处理: {self.stats['successful_processing']}")
|
||
print(f" 失败处理: {self.stats['failed_processing']}")
|
||
|
||
success_rate = (self.stats['successful_processing'] /
|
||
max(1, self.stats['successful_processing'] + self.stats['failed_processing']) * 100)
|
||
print(f" 成功率: {success_rate:.1f}%")
|
||
|
||
print("👋 系统已关闭")
|
||
|
||
def main():
|
||
"""主函数"""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description='多进程音频控制系统')
|
||
parser.add_argument('--character', '-c', type=str, default='libai',
|
||
help='选择角色 (默认: libai)')
|
||
parser.add_argument('--config', type=str,
|
||
help='配置文件路径')
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 加载配置
|
||
config = None
|
||
if args.config:
|
||
try:
|
||
with open(args.config, 'r', encoding='utf-8') as f:
|
||
config = json.load(f)
|
||
except Exception as e:
|
||
print(f"⚠️ 配置文件加载失败: {e}")
|
||
|
||
# 创建控制系统
|
||
control_system = ControlSystem(config)
|
||
|
||
# 设置角色
|
||
if args.character:
|
||
control_system.config['processing']['character'] = args.character
|
||
|
||
# 启动系统
|
||
control_system.start()
|
||
|
||
if __name__ == "__main__":
|
||
main() |