|
@@ -29,6 +29,7 @@ class WebSocketHandler:
|
|
|
self.cancelled_requests: set = set() # 已取消的请求ID
|
|
self.cancelled_requests: set = set() # 已取消的请求ID
|
|
|
self.recognition_sessions: Dict[str, any] = {} # requestId -> Recognition对象
|
|
self.recognition_sessions: Dict[str, any] = {} # requestId -> Recognition对象
|
|
|
self.recognition_results: Dict[str, str] = {} # requestId -> 识别结果文本
|
|
self.recognition_results: Dict[str, str] = {} # requestId -> 识别结果文本
|
|
|
|
|
+ self.recognition_sentences: Dict[str, list] = {} # requestId -> 句子列表
|
|
|
self.recognition_failed: set = set() # STT识别失败的请求ID
|
|
self.recognition_failed: set = set() # STT识别失败的请求ID
|
|
|
self.event_loop: Optional[asyncio.AbstractEventLoop] = None # 保存事件循环引用
|
|
self.event_loop: Optional[asyncio.AbstractEventLoop] = None # 保存事件循环引用
|
|
|
self.request_start_times: Dict[str, float] = {} # 请求开始时间
|
|
self.request_start_times: Dict[str, float] = {} # 请求开始时间
|
|
@@ -137,6 +138,9 @@ class WebSocketHandler:
|
|
|
request_id = message.requestId
|
|
request_id = message.requestId
|
|
|
import time
|
|
import time
|
|
|
|
|
|
|
|
|
|
+ # 记录录音结束时间
|
|
|
|
|
+ audio_end_time = time.time()
|
|
|
|
|
+
|
|
|
# 统计音频流信息
|
|
# 统计音频流信息
|
|
|
total_size = len(self.audio_buffers.get(request_id, bytearray()))
|
|
total_size = len(self.audio_buffers.get(request_id, bytearray()))
|
|
|
duration_seconds = total_size / (16000 * 2)
|
|
duration_seconds = total_size / (16000 * 2)
|
|
@@ -155,6 +159,11 @@ class WebSocketHandler:
|
|
|
try:
|
|
try:
|
|
|
# 1. STT: 等待完整识别结果(保证准确性)
|
|
# 1. STT: 等待完整识别结果(保证准确性)
|
|
|
stt_start = time.time()
|
|
stt_start = time.time()
|
|
|
|
|
+
|
|
|
|
|
+ # 等待一小段时间,让STT处理完最后的音频帧
|
|
|
|
|
+ print(f"[{request_id}] ⏳ 等待STT处理最后的音频帧...")
|
|
|
|
|
+ await asyncio.sleep(0.3) # 300ms缓冲时间
|
|
|
|
|
+
|
|
|
await self.stop_streaming_stt(request_id)
|
|
await self.stop_streaming_stt(request_id)
|
|
|
|
|
|
|
|
# 获取STT识别结果
|
|
# 获取STT识别结果
|
|
@@ -197,6 +206,7 @@ class WebSocketHandler:
|
|
|
tts_start = time.time()
|
|
tts_start = time.time()
|
|
|
|
|
|
|
|
chunk_index = 0
|
|
chunk_index = 0
|
|
|
|
|
+ first_audio_time = None
|
|
|
async for audio_chunk in ai_service.text_to_speech_stream(response_text, emotion):
|
|
async for audio_chunk in ai_service.text_to_speech_stream(response_text, emotion):
|
|
|
if request_id in self.cancelled_requests:
|
|
if request_id in self.cancelled_requests:
|
|
|
return
|
|
return
|
|
@@ -211,6 +221,13 @@ class WebSocketHandler:
|
|
|
chunk_index,
|
|
chunk_index,
|
|
|
False,
|
|
False,
|
|
|
)
|
|
)
|
|
|
|
|
+
|
|
|
|
|
+ # 记录第一个音频块发送时间
|
|
|
|
|
+ if chunk_index == 0:
|
|
|
|
|
+ first_audio_time = time.time()
|
|
|
|
|
+ time_to_first_audio = first_audio_time - audio_end_time
|
|
|
|
|
+ print(f"[{request_id}] 🎵 第一个音频块已发送 [耗时: {time_to_first_audio:.2f}秒 = 录音结束→第一个音频块]")
|
|
|
|
|
+
|
|
|
chunk_index += 1
|
|
chunk_index += 1
|
|
|
|
|
|
|
|
tts_end = time.time()
|
|
tts_end = time.time()
|
|
@@ -220,10 +237,27 @@ class WebSocketHandler:
|
|
|
# 发送TTS结束标记
|
|
# 发送TTS结束标记
|
|
|
await self.send_audio_response(request_id, "", chunk_index, True)
|
|
await self.send_audio_response(request_id, "", chunk_index, True)
|
|
|
|
|
|
|
|
|
|
+ # 记录最后一个音频块发送时间
|
|
|
|
|
+ last_audio_time = time.time()
|
|
|
|
|
+ time_to_last_audio = last_audio_time - audio_end_time
|
|
|
|
|
+ print(f"[{request_id}] 🎵 最后一个音频块已发送 [耗时: {time_to_last_audio:.2f}秒 = 录音结束→最后一个音频块]")
|
|
|
|
|
+
|
|
|
# 计算总耗时
|
|
# 计算总耗时
|
|
|
total_end = time.time()
|
|
total_end = time.time()
|
|
|
total_duration = total_end - self.request_start_times.get(request_id, stt_start)
|
|
total_duration = total_end - self.request_start_times.get(request_id, stt_start)
|
|
|
- print(f"[{request_id}] ⏱️ 总耗时: {total_duration:.2f}秒 (STT: {stt_duration:.2f}s + LLM: {llm_duration:.2f}s + TTS: {tts_duration:.2f}s)")
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 性能汇总日志
|
|
|
|
|
+ print(f"\n{'='*60}")
|
|
|
|
|
+ print(f"[{request_id}] 📊 性能汇总报告")
|
|
|
|
|
+ print(f"{'='*60}")
|
|
|
|
|
+ print(f" STT 耗时: {stt_duration:.2f}秒")
|
|
|
|
|
+ print(f" LLM 耗时: {llm_duration:.2f}秒")
|
|
|
|
|
+ print(f" TTS 耗时: {tts_duration:.2f}秒")
|
|
|
|
|
+ if first_audio_time:
|
|
|
|
|
+ print(f" 🎯 录音结束→首个音频: {time_to_first_audio:.2f}秒 ⭐")
|
|
|
|
|
+ print(f" 🎯 录音结束→全部音频: {time_to_last_audio:.2f}秒")
|
|
|
|
|
+ print(f" 总耗时: {total_duration:.2f}秒")
|
|
|
|
|
+ print(f"{'='*60}")
|
|
|
print(f"[{request_id}] ✅ 请求处理完成\n")
|
|
print(f"[{request_id}] ✅ 请求处理完成\n")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
@@ -270,18 +304,44 @@ class WebSocketHandler:
|
|
|
sentence = result.get_sentence()
|
|
sentence = result.get_sentence()
|
|
|
if sentence and 'text' in sentence:
|
|
if sentence and 'text' in sentence:
|
|
|
text = sentence['text']
|
|
text = sentence['text']
|
|
|
- # 判断是否为最终结果(end_time 可能为 None)
|
|
|
|
|
|
|
+ begin_time = sentence.get('begin_time', 0)
|
|
|
end_time = sentence.get('end_time')
|
|
end_time = sentence.get('end_time')
|
|
|
is_final = end_time is not None and end_time > 0
|
|
is_final = end_time is not None and end_time > 0
|
|
|
status = "✅最终" if is_final else "⏳中间"
|
|
status = "✅最终" if is_final else "⏳中间"
|
|
|
- print(f"[{req_id}] 💬 STT识别({status}): {text}")
|
|
|
|
|
- # 保存最新的识别结果
|
|
|
|
|
- handler.recognition_results[req_id] = text
|
|
|
|
|
|
|
+
|
|
|
|
|
+ print(f"[{req_id}] 💬 STT识别({status}): {text} [时间: {begin_time}-{end_time}]")
|
|
|
|
|
+
|
|
|
|
|
+ # 初始化句子列表
|
|
|
|
|
+ if req_id not in handler.recognition_sentences:
|
|
|
|
|
+ handler.recognition_sentences[req_id] = []
|
|
|
|
|
+
|
|
|
|
|
+ sentence_list = handler.recognition_sentences[req_id]
|
|
|
|
|
+
|
|
|
|
|
+ # 查找是否是已有句子的更新(根据begin_time匹配)
|
|
|
|
|
+ updated = False
|
|
|
|
|
+ for i, sent in enumerate(sentence_list):
|
|
|
|
|
+ if abs(sent['begin_time'] - begin_time) < 100: # 100ms容差
|
|
|
|
|
+ # 更新现有句子
|
|
|
|
|
+ sentence_list[i] = {'text': text, 'begin_time': begin_time, 'end_time': end_time}
|
|
|
|
|
+ updated = True
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ if not updated:
|
|
|
|
|
+ # 新句子,追加
|
|
|
|
|
+ sentence_list.append({'text': text, 'begin_time': begin_time, 'end_time': end_time})
|
|
|
|
|
+
|
|
|
|
|
+ # 按时间排序并合并所有句子
|
|
|
|
|
+ sentence_list.sort(key=lambda x: x['begin_time'])
|
|
|
|
|
+ accumulated_text = ' '.join([s['text'] for s in sentence_list])
|
|
|
|
|
+
|
|
|
|
|
+ handler.recognition_results[req_id] = accumulated_text
|
|
|
|
|
+ print(f"[{req_id}] 📝 累积文本: {accumulated_text}")
|
|
|
|
|
+
|
|
|
# 从回调线程安全地发送到事件循环
|
|
# 从回调线程安全地发送到事件循环
|
|
|
if handler.event_loop:
|
|
if handler.event_loop:
|
|
|
try:
|
|
try:
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
asyncio.run_coroutine_threadsafe(
|
|
|
- handler.send_text_result(req_id, text, False),
|
|
|
|
|
|
|
+ handler.send_text_result(req_id, accumulated_text, False),
|
|
|
handler.event_loop
|
|
handler.event_loop
|
|
|
)
|
|
)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
@@ -332,6 +392,10 @@ class WebSocketHandler:
|
|
|
import time
|
|
import time
|
|
|
stop_start = time.time()
|
|
stop_start = time.time()
|
|
|
await asyncio.to_thread(recognition.stop)
|
|
await asyncio.to_thread(recognition.stop)
|
|
|
|
|
+
|
|
|
|
|
+ # 停止后再等待一小段时间,确保最后的识别结果回调被触发
|
|
|
|
|
+ await asyncio.sleep(0.2) # 200ms等待最终结果
|
|
|
|
|
+
|
|
|
stop_duration = (time.time() - stop_start) * 1000
|
|
stop_duration = (time.time() - stop_start) * 1000
|
|
|
print(f"[{request_id}] ✅ STT识别已停止 (耗时: {stop_duration:.1f}ms)")
|
|
print(f"[{request_id}] ✅ STT识别已停止 (耗时: {stop_duration:.1f}ms)")
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
@@ -351,6 +415,10 @@ class WebSocketHandler:
|
|
|
if request_id in self.recognition_results:
|
|
if request_id in self.recognition_results:
|
|
|
del self.recognition_results[request_id]
|
|
del self.recognition_results[request_id]
|
|
|
|
|
|
|
|
|
|
+ # 清理句子列表
|
|
|
|
|
+ if request_id in self.recognition_sentences:
|
|
|
|
|
+ del self.recognition_sentences[request_id]
|
|
|
|
|
+
|
|
|
# 清理取消标记
|
|
# 清理取消标记
|
|
|
if request_id in self.cancelled_requests:
|
|
if request_id in self.cancelled_requests:
|
|
|
self.cancelled_requests.remove(request_id)
|
|
self.cancelled_requests.remove(request_id)
|