This commit is contained in:
Chingfeng Li
2025-10-24 09:02:09 +08:00
2 changed files with 194 additions and 109 deletions
@@ -22,6 +22,7 @@ STATUS_FIRST_FRAME = 0 # 第一帧的标识
STATUS_CONTINUE_FRAME = 1 # 中间帧标识 STATUS_CONTINUE_FRAME = 1 # 中间帧标识
STATUS_LAST_FRAME = 2 # 最后一帧的标识 STATUS_LAST_FRAME = 2 # 最后一帧的标识
class ASRProvider(ASRProviderBase): class ASRProvider(ASRProviderBase):
def __init__(self, config, delete_audio_file): def __init__(self, config, delete_audio_file):
super().__init__() super().__init__()
@@ -35,6 +36,7 @@ class ASRProvider(ASRProviderBase):
self.server_ready = False self.server_ready = False
self.last_frame_sent = False # 标记是否已发送最终帧 self.last_frame_sent = False # 标记是否已发送最终帧
self.best_text = "" # 保存最佳识别结果 self.best_text = "" # 保存最佳识别结果
self.has_final_result = False # 标记是否收到最终识别结果
# 讯飞配置 # 讯飞配置
self.app_id = config.get("app_id") self.app_id = config.get("app_id")
@@ -50,19 +52,15 @@ class ASRProvider(ASRProviderBase):
"language": config.get("language", "zh_cn"), "language": config.get("language", "zh_cn"),
"accent": config.get("accent", "mandarin"), "accent": config.get("accent", "mandarin"),
"dwa": config.get("dwa", "wpgs"), "dwa": config.get("dwa", "wpgs"),
"result": { "result": {"encoding": "utf8", "compress": "raw", "format": "plain"},
"encoding": "utf8",
"compress": "raw",
"format": "plain"
}
} }
self.output_dir = config.get("output_dir", "tmp/") self.output_dir = config.get("output_dir", "tmp/")
self.delete_audio_file = delete_audio_file self.delete_audio_file = delete_audio_file
def create_url(self) -> str: def create_url(self) -> str:
"""生成认证URL""" """生成认证URL"""
url = 'ws://iat.cn-huabei-1.xf-yun.com/v1' url = "ws://iat.cn-huabei-1.xf-yun.com/v1"
# 生成RFC1123格式的时间戳 # 生成RFC1123格式的时间戳
now = datetime.now() now = datetime.now()
date = format_date_time(mktime(now.timetuple())) date = format_date_time(mktime(now.timetuple()))
@@ -73,23 +71,30 @@ class ASRProvider(ASRProviderBase):
signature_origin += "GET " + "/v1 " + "HTTP/1.1" signature_origin += "GET " + "/v1 " + "HTTP/1.1"
# 进行hmac-sha256进行加密 # 进行hmac-sha256进行加密
signature_sha = hmac.new(self.api_secret.encode('utf-8'), signature_origin.encode('utf-8'), signature_sha = hmac.new(
digestmod=hashlib.sha256).digest() self.api_secret.encode("utf-8"),
signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8') signature_origin.encode("utf-8"),
digestmod=hashlib.sha256,
).digest()
signature_sha = base64.b64encode(signature_sha).decode(encoding="utf-8")
authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % ( authorization_origin = (
self.api_key, "hmac-sha256", "host date request-line", signature_sha) 'api_key="%s", algorithm="%s", headers="%s", signature="%s"'
authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8') % (self.api_key, "hmac-sha256", "host date request-line", signature_sha)
)
authorization = base64.b64encode(authorization_origin.encode("utf-8")).decode(
encoding="utf-8"
)
# 将请求的鉴权参数组合为字典 # 将请求的鉴权参数组合为字典
v = { v = {
"authorization": authorization, "authorization": authorization,
"date": date, "date": date,
"host": "iat.cn-huabei-1.xf-yun.com" "host": "iat.cn-huabei-1.xf-yun.com",
} }
# 拼接鉴权参数,生成url # 拼接鉴权参数,生成url
url = url + '?' + urlencode(v) url = url + "?" + urlencode(v)
return url return url
async def open_audio_channels(self, conn): async def open_audio_channels(self, conn):
@@ -100,7 +105,7 @@ class ASRProvider(ASRProviderBase):
await super().receive_audio(conn, audio, audio_have_voice) await super().receive_audio(conn, audio, audio_have_voice)
# 存储音频数据用于声纹识别 # 存储音频数据用于声纹识别
if not hasattr(conn, 'asr_audio_for_voiceprint'): if not hasattr(conn, "asr_audio_for_voiceprint"):
conn.asr_audio_for_voiceprint = [] conn.asr_audio_for_voiceprint = []
conn.asr_audio_for_voiceprint.append(audio) conn.asr_audio_for_voiceprint.append(audio)
@@ -146,8 +151,10 @@ class ASRProvider(ASRProviderBase):
# 发送首帧音频 # 发送首帧音频
if conn.asr_audio and len(conn.asr_audio) > 0: if conn.asr_audio and len(conn.asr_audio) > 0:
first_audio = conn.asr_audio[-1] if conn.asr_audio else b'' first_audio = conn.asr_audio[-1] if conn.asr_audio else b""
pcm_frame = self.decoder.decode(first_audio, 960) if first_audio else b'' pcm_frame = (
self.decoder.decode(first_audio, 960) if first_audio else b""
)
await self._send_audio_frame(pcm_frame, STATUS_FIRST_FRAME) await self._send_audio_frame(pcm_frame, STATUS_FIRST_FRAME)
self.server_ready = True self.server_ready = True
logger.bind(tag=TAG).info("已发送首帧,开始识别") logger.bind(tag=TAG).info("已发送首帧,开始识别")
@@ -176,23 +183,14 @@ class ASRProvider(ASRProviderBase):
if not self.asr_ws: if not self.asr_ws:
return return
audio_b64 = base64.b64encode(audio_data).decode('utf-8') audio_b64 = base64.b64encode(audio_data).decode("utf-8")
frame_data = { frame_data = {
"header": { "header": {"status": status, "app_id": self.app_id},
"status": status, "parameter": {"iat": self.iat_params},
"app_id": self.app_id
},
"parameter": {
"iat": self.iat_params
},
"payload": { "payload": {
"audio": { "audio": {"audio": audio_b64, "sample_rate": 16000, "encoding": "raw"}
"audio": audio_b64, },
"sample_rate": 16000,
"encoding": "raw"
}
}
} }
await self.asr_ws.send(json.dumps(frame_data, ensure_ascii=False)) await self.asr_ws.send(json.dumps(frame_data, ensure_ascii=False))
@@ -207,11 +205,13 @@ class ASRProvider(ASRProviderBase):
try: try:
while self.asr_ws and not conn.stop_event.is_set(): while self.asr_ws and not conn.stop_event.is_set():
# 获取当前连接的音频数据 # 获取当前连接的音频数据
audio_data = getattr(conn, 'asr_audio_for_voiceprint', []) audio_data = getattr(conn, "asr_audio_for_voiceprint", [])
try: try:
# 如果已发送最终帧,增加超时时间等待完整结果 # 如果已发送最终帧,增加超时时间等待完整结果
timeout = 3.0 if self.last_frame_sent else 30.0 timeout = 3.0 if self.last_frame_sent else 30.0
response = await asyncio.wait_for(self.asr_ws.recv(), timeout=timeout) response = await asyncio.wait_for(
self.asr_ws.recv(), timeout=timeout
)
result = json.loads(response) result = json.loads(response)
logger.bind(tag=TAG).debug(f"收到ASR结果: {result}") logger.bind(tag=TAG).debug(f"收到ASR结果: {result}")
@@ -221,7 +221,9 @@ class ASRProvider(ASRProviderBase):
status = header.get("status", 0) status = header.get("status", 0)
if code != 0: if code != 0:
logger.bind(tag=TAG).error(f"识别错误,错误码: {code}, 消息: {header.get('message', '')}") logger.bind(tag=TAG).error(
f"识别错误,错误码: {code}, 消息: {header.get('message', '')}"
)
if code in [10114, 10160]: # 连接问题 if code in [10114, 10160]: # 连接问题
break break
continue continue
@@ -231,52 +233,124 @@ class ASRProvider(ASRProviderBase):
text_data = payload["result"]["text"] text_data = payload["result"]["text"]
if text_data: if text_data:
# 解码base64文本 # 解码base64文本
decoded_text = base64.b64decode(text_data).decode('utf-8') decoded_text = base64.b64decode(text_data).decode("utf-8")
text_json = json.loads(decoded_text) text_json = json.loads(decoded_text)
# 提取文本内容 # 提取文本内容
text_ws = text_json.get('ws', []) text_ws = text_json.get("ws", [])
result_text = '' result_text = ""
for i in text_ws: for i in text_ws:
for j in i.get("cw", []): for j in i.get("cw", []):
w = j.get("w", "") w = j.get("w", "")
result_text += w result_text += w
# 更新识别文本 - 实时更新策略 # 更新识别文本 - 实时更新策略
if result_text and result_text.strip() not in ['', '', '.', ',', '']: # 只检查是否为空字符串,不再过滤任何标点符号
# 这样可以确保所有识别到的内容,包括标点符号都能被实时更新
if result_text and result_text.strip():
# 实时更新:正常情况下都更新,提高响应速度 # 实时更新:正常情况下都更新,提高响应速度
should_update = True should_update = True
# 保存最佳文本(最长的有意义文本) # 保存最佳文本
if (len(result_text) > len(self.best_text) and # 1. 如果是识别完成状态或最终帧后收到的结果,优先保存
result_text.strip() not in ['', '?', '', '.']): # 2. 否则保存最长的有意义文本
self.best_text = result_text # 取消对标点符号的过滤,只检查是否为空
logger.bind(tag=TAG).debug(f"保存最佳文本: {self.best_text}") # 这样可以保留所有识别到的内容,包括各种标点符号
is_valid_text = len(result_text.strip()) > 0
# 如果已发送最终帧,只过滤明显的无效结果 if (
self.last_frame_sent or status == 2
) and is_valid_text:
self.best_text = result_text
self.has_final_result = True # 标记已收到最终结果
logger.bind(tag=TAG).debug(
f"保存最终识别结果: {self.best_text}"
)
elif (
len(result_text) > len(self.best_text)
and is_valid_text
and not self.has_final_result
):
self.best_text = result_text
logger.bind(tag=TAG).debug(
f"保存中间最佳文本: {self.best_text}"
)
# 如果已发送最终帧,只过滤空文本
if self.last_frame_sent: if self.last_frame_sent:
# 最终帧后拒绝问号等明显错误的结果 # 只拒绝完全空的结果
if result_text.strip() in ['', '?', '', '.']: if not result_text.strip():
should_update = False should_update = False
logger.bind(tag=TAG).warning(f"最终帧后拒绝无效文本: {result_text}") logger.bind(tag=TAG).warning(
f"最终帧后拒绝空文本"
)
if should_update: if should_update:
self.text = result_text # 处理流式识别结果,避免简单替换导致内容丢失
logger.bind(tag=TAG).info(f"实时更新识别文本: {self.text} (最终帧已发送: {self.last_frame_sent})") # 1. 如果是中间状态(非最终帧后),可能需要替换为更完整的识别
# 2. 如果是最终帧后收到的结果,可能是对前面文本的补充
if self.last_frame_sent:
# 最终帧后收到的结果可能是标点符号等补充内容
# 检查是否需要合并文本而不是替换
# 如果当前文本是纯标点而前面已有内容,应该追加而不是替换
if len(
self.text
) > 0 and result_text.strip() in [
"",
".",
"?",
"",
"!",
"",
",",
"",
";",
"",
]:
# 对于标点符号,追加到现有文本后
self.text = (
self.text.rstrip().rstrip("。.")
+ result_text
)
else:
# 其他情况保持替换逻辑
self.text = result_text
else:
# 中间状态替换为新的识别结果
self.text = result_text
logger.bind(tag=TAG).info(
f"实时更新识别文本: {self.text} (最终帧已发送: {self.last_frame_sent})"
)
# 识别完成,但如果还没发送最终帧,继续等待 # 识别完成,但如果还没发送最终帧,继续等待
if status == 2: if status == 2:
logger.bind(tag=TAG).info(f"识别完成状态已到达,当前识别文本: {self.text}") logger.bind(tag=TAG).info(
f"识别完成状态已到达,当前识别文本: {self.text}"
)
# 如果还没发送最终帧,继续等待 # 如果还没发送最终帧,继续等待
if not self.last_frame_sent: if not self.last_frame_sent:
logger.bind(tag=TAG).info("识别完成但最终帧未发送,继续等待...") logger.bind(tag=TAG).info(
"识别完成但最终帧未发送,继续等待..."
)
continue continue
# 已发送最终帧且收到完成状态,使用最佳文本作为最终结果 # 已发送最终帧且收到完成状态,使用最佳策略选择最终结果
if self.best_text and len(self.best_text) > len(self.text): # 优先使用识别完成状态下的最新结果,而不是仅仅基于长度
logger.bind(tag=TAG).info(f"使用最佳文本作为最终结果: {self.text} -> {self.best_text}") if self.best_text:
self.text = self.best_text # 如果当前文本是在最终帧发送后或识别完成状态下收到的,优先使用
if (
self.last_frame_sent or status == 2
) and self.text.strip():
logger.bind(tag=TAG).info(
f"使用完成状态下的最新识别结果: {self.text}"
)
elif len(self.best_text) > len(self.text):
logger.bind(tag=TAG).info(
f"使用更长的最佳文本作为最终结果: {self.text} -> {self.best_text}"
)
self.text = self.best_text
logger.bind(tag=TAG).info(f"获取到最终完整文本: {self.text}") logger.bind(tag=TAG).info(f"获取到最终完整文本: {self.text}")
conn.reset_vad_states() conn.reset_vad_states()
@@ -289,9 +363,13 @@ class ASRProvider(ASRProviderBase):
if self.last_frame_sent: if self.last_frame_sent:
# 超时时也使用最佳文本 # 超时时也使用最佳文本
if self.best_text and len(self.best_text) > len(self.text): if self.best_text and len(self.best_text) > len(self.text):
logger.bind(tag=TAG).info(f"超时,使用最佳文本: {self.text} -> {self.best_text}") logger.bind(tag=TAG).info(
f"超时,使用最佳文本: {self.text} -> {self.best_text}"
)
self.text = self.best_text self.text = self.best_text
logger.bind(tag=TAG).info(f"最终帧后超时,使用结果: {self.text}") logger.bind(tag=TAG).info(
f"最终帧后超时,使用结果: {self.text}"
)
break break
# 如果还没发送最终帧,继续等待 # 如果还没发送最终帧,继续等待
continue continue
@@ -316,11 +394,11 @@ class ASRProvider(ASRProviderBase):
self.asr_ws = None self.asr_ws = None
self.is_processing = False self.is_processing = False
if conn: if conn:
if hasattr(conn, 'asr_audio_for_voiceprint'): if hasattr(conn, "asr_audio_for_voiceprint"):
conn.asr_audio_for_voiceprint = [] conn.asr_audio_for_voiceprint = []
if hasattr(conn, 'asr_audio'): if hasattr(conn, "asr_audio"):
conn.asr_audio = [] conn.asr_audio = []
if hasattr(conn, 'has_valid_voice'): if hasattr(conn, "has_valid_voice"):
conn.has_valid_voice = False conn.has_valid_voice = False
async def handle_voice_stop(self, conn, asr_audio_task: List[bytes]): async def handle_voice_stop(self, conn, asr_audio_task: List[bytes]):
@@ -330,7 +408,7 @@ class ASRProvider(ASRProviderBase):
if self.asr_ws and self.is_processing: if self.asr_ws and self.is_processing:
try: try:
# 取最后一个有效的音频帧作为最后一帧数据 # 取最后一个有效的音频帧作为最后一帧数据
last_frame = b'' last_frame = b""
if asr_audio_task: if asr_audio_task:
last_audio = asr_audio_task[-1] last_audio = asr_audio_task[-1]
last_frame = self.decoder.decode(last_audio, 960) last_frame = self.decoder.decode(last_audio, 960)
@@ -338,8 +416,7 @@ class ASRProvider(ASRProviderBase):
logger.bind(tag=TAG).info("已发送最后一帧") logger.bind(tag=TAG).info("已发送最后一帧")
# 发送最终帧后,给_forward_results适当时间处理最终结果 # 发送最终帧后,给_forward_results适当时间处理最终结果
# 减少等待时间,提高响应速度 await asyncio.sleep(0.25)
await asyncio.sleep(0.1) # 从3秒减少到1秒
logger.bind(tag=TAG).info(f"准备处理最终识别结果: {self.text}") logger.bind(tag=TAG).info(f"准备处理最终识别结果: {self.text}")
except Exception as e: except Exception as e:
@@ -350,8 +427,9 @@ class ASRProvider(ASRProviderBase):
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"处理语音停止失败: {e}") logger.bind(tag=TAG).error(f"处理语音停止失败: {e}")
import traceback import traceback
logger.bind(tag=TAG).debug(f"异常详情: {traceback.format_exc()}") logger.bind(tag=TAG).debug(f"异常详情: {traceback.format_exc()}")
def stop_ws_connection(self): def stop_ws_connection(self):
if self.asr_ws: if self.asr_ws:
asyncio.create_task(self.asr_ws.close()) asyncio.create_task(self.asr_ws.close())
@@ -360,12 +438,14 @@ class ASRProvider(ASRProviderBase):
async def _cleanup(self, conn): async def _cleanup(self, conn):
"""清理资源""" """清理资源"""
logger.bind(tag=TAG).info(f"开始ASR会话清理 | 当前状态: processing={self.is_processing}, server_ready={self.server_ready}") logger.bind(tag=TAG).info(
f"开始ASR会话清理 | 当前状态: processing={self.is_processing}, server_ready={self.server_ready}"
)
# 发送最后一帧 # 发送最后一帧
if self.asr_ws and self.is_processing: if self.asr_ws and self.is_processing:
try: try:
await self._send_audio_frame(b'', STATUS_LAST_FRAME) await self._send_audio_frame(b"", STATUS_LAST_FRAME)
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
logger.bind(tag=TAG).info("已发送最后一帧") logger.bind(tag=TAG).info("已发送最后一帧")
except Exception as e: except Exception as e:
@@ -376,6 +456,7 @@ class ASRProvider(ASRProviderBase):
self.server_ready = False self.server_ready = False
self.last_frame_sent = False self.last_frame_sent = False
self.best_text = "" self.best_text = ""
self.has_final_result = False
logger.bind(tag=TAG).info("ASR状态已重置") logger.bind(tag=TAG).info("ASR状态已重置")
# 清理任务 # 清理任务
@@ -403,11 +484,11 @@ class ASRProvider(ASRProviderBase):
# 清理连接的音频缓存 # 清理连接的音频缓存
if conn: if conn:
if hasattr(conn, 'asr_audio_for_voiceprint'): if hasattr(conn, "asr_audio_for_voiceprint"):
conn.asr_audio_for_voiceprint = [] conn.asr_audio_for_voiceprint = []
if hasattr(conn, 'asr_audio'): if hasattr(conn, "asr_audio"):
conn.asr_audio = [] conn.asr_audio = []
if hasattr(conn, 'has_valid_voice'): if hasattr(conn, "has_valid_voice"):
conn.has_valid_voice = False conn.has_valid_voice = False
logger.bind(tag=TAG).info("ASR会话清理完成") logger.bind(tag=TAG).info("ASR会话清理完成")
@@ -432,11 +513,11 @@ class ASRProvider(ASRProviderBase):
self.forward_task = None self.forward_task = None
self.is_processing = False self.is_processing = False
# 清理所有连接的音频缓冲区 # 清理所有连接的音频缓冲区
if hasattr(self, '_connections'): if hasattr(self, "_connections"):
for conn in self._connections.values(): for conn in self._connections.values():
if hasattr(conn, 'asr_audio_for_voiceprint'): if hasattr(conn, "asr_audio_for_voiceprint"):
conn.asr_audio_for_voiceprint = [] conn.asr_audio_for_voiceprint = []
if hasattr(conn, 'asr_audio'): if hasattr(conn, "asr_audio"):
conn.asr_audio = [] conn.asr_audio = []
if hasattr(conn, 'has_valid_voice'): if hasattr(conn, "has_valid_voice"):
conn.has_valid_voice = False conn.has_valid_voice = False
@@ -149,6 +149,7 @@ class TTSProvider(TTSProviderBase):
self.access_token = config.get("access_token") self.access_token = config.get("access_token")
self.cluster = config.get("cluster") self.cluster = config.get("cluster")
self.resource_id = config.get("resource_id") self.resource_id = config.get("resource_id")
self.activate_session = False
if config.get("private_voice"): if config.get("private_voice"):
self.voice = config.get("private_voice") self.voice = config.get("private_voice")
else: else:
@@ -180,7 +181,7 @@ class TTSProvider(TTSProviderBase):
raise raise
async def _ensure_connection(self): async def _ensure_connection(self):
"""建立新的WebSocket连接""" """建立新的WebSocket连接,并启动监听任务(仅第一次)"""
try: try:
if self.ws: if self.ws:
logger.bind(tag=TAG).info(f"使用已有链接...") logger.bind(tag=TAG).info(f"使用已有链接...")
@@ -196,6 +197,12 @@ class TTSProvider(TTSProviderBase):
self.ws_url, additional_headers=ws_header, max_size=1000000000 self.ws_url, additional_headers=ws_header, max_size=1000000000
) )
logger.bind(tag=TAG).info("WebSocket连接建立成功") logger.bind(tag=TAG).info("WebSocket连接建立成功")
# 连接建立成功后,启动监听任务
if self._monitor_task is None or self._monitor_task.done():
logger.bind(tag=TAG).info("启动监听任务...")
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response())
return self.ws return self.ws
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"建立连接失败: {str(e)}") logger.bind(tag=TAG).error(f"建立连接失败: {str(e)}")
@@ -314,22 +321,24 @@ class TTSProvider(TTSProviderBase):
async def start_session(self, session_id): async def start_session(self, session_id):
logger.bind(tag=TAG).info(f"开始会话~~{session_id}") logger.bind(tag=TAG).info(f"开始会话~~{session_id}")
try: try:
# 会话开始时检测上个会话的监听状态 # 等待上一个会话结束,最多等待3次
if ( for _ in range(3):
self._monitor_task is not None if not self.activate_session:
and isinstance(self._monitor_task, Task) break
and not self._monitor_task.done() logger.bind(tag=TAG).debug(f"等待上一个会话结束...")
): await asyncio.sleep(0.1)
logger.bind(tag=TAG).info("检测到未完成的上个会话,关闭监听任务和连接...") else:
# 等待超时,强制清除连接状态
logger.bind(tag=TAG).debug("等待上一个会话超时,清除连接状态...")
await self.close() await self.close()
# 建立新连接 # 设置会话激活标志
self.activate_session = True
# 确保连接建立
await self._ensure_connection() await self._ensure_connection()
# 启动监听任务
self._monitor_task = asyncio.create_task(self._start_monitor_tts_response())
header = Header( header = Header(
message_type=FULL_CLIENT_REQUEST, message_type=FULL_CLIENT_REQUEST,
message_type_specific_flags=MsgTypeFlagWithEvent, message_type_specific_flags=MsgTypeFlagWithEvent,
@@ -365,17 +374,6 @@ class TTSProvider(TTSProviderBase):
await self.send_event(self.ws, header, optional, payload) await self.send_event(self.ws, header, optional, payload)
logger.bind(tag=TAG).info("会话结束请求已发送") logger.bind(tag=TAG).info("会话结束请求已发送")
# 等待监听任务完成
if self._monitor_task:
try:
await self._monitor_task
except Exception as e:
logger.bind(tag=TAG).error(
f"等待监听任务完成时发生错误: {str(e)}"
)
finally:
self._monitor_task = None
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}") logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}")
# 确保清理资源 # 确保清理资源
@@ -405,6 +403,7 @@ class TTSProvider(TTSProviderBase):
async def close(self): async def close(self):
"""资源清理方法""" """资源清理方法"""
self.activate_session = False
# 取消监听任务 # 取消监听任务
if self._monitor_task: if self._monitor_task:
try: try:
@@ -424,9 +423,8 @@ class TTSProvider(TTSProviderBase):
self.ws = None self.ws = None
async def _start_monitor_tts_response(self): async def _start_monitor_tts_response(self):
"""监听TTS响应""" """监听TTS响应 - 长期运行"""
try: try:
session_finished = False # 标记会话是否正常结束
while not self.conn.stop_event.is_set(): while not self.conn.stop_event.is_set():
try: try:
# 确保 `recv()` 运行在同一个 event loop # 确保 `recv()` 运行在同一个 event loop
@@ -434,10 +432,17 @@ class TTSProvider(TTSProviderBase):
res = self.parser_response(msg) res = self.parser_response(msg)
self.print_response(res, "send_text res:") self.print_response(res, "send_text res:")
# 只处理当前活跃会话的响应
if res.optional.sessionId and self.conn.sentence_id != res.optional.sessionId:
# 如果是会话结束相关事件,即使会话ID不匹配也要重置状态
if res.optional.event in [EVENT_SessionCanceled, EVENT_SessionFailed, EVENT_SessionFinished]:
logger.bind(tag=TAG).debug(f"收到残余下行结束响应重置会话状态~~")
self.activate_session = False
continue
if res.optional.event == EVENT_SessionCanceled: if res.optional.event == EVENT_SessionCanceled:
logger.bind(tag=TAG).debug(f"释放服务端资源成功~~") logger.bind(tag=TAG).debug(f"释放服务端资源成功~~")
session_finished = True self.activate_session = False
break
elif res.optional.event == EVENT_TTSSentenceStart: elif res.optional.event == EVENT_TTSSentenceStart:
json_data = json.loads(res.payload.decode("utf-8")) json_data = json.loads(res.payload.decode("utf-8"))
self.tts_text = json_data.get("text", "") self.tts_text = json_data.get("text", "")
@@ -454,9 +459,8 @@ class TTSProvider(TTSProviderBase):
logger.bind(tag=TAG).info(f"句子语音生成成功:{self.tts_text}") logger.bind(tag=TAG).info(f"句子语音生成成功:{self.tts_text}")
elif res.optional.event == EVENT_SessionFinished: elif res.optional.event == EVENT_SessionFinished:
logger.bind(tag=TAG).debug(f"会话结束~~") logger.bind(tag=TAG).debug(f"会话结束~~")
self.activate_session = False
self._process_before_stop_play_files() self._process_before_stop_play_files()
session_finished = True
break
except websockets.ConnectionClosed: except websockets.ConnectionClosed:
logger.bind(tag=TAG).warning("WebSocket连接已关闭") logger.bind(tag=TAG).warning("WebSocket连接已关闭")
break break
@@ -466,8 +470,8 @@ class TTSProvider(TTSProviderBase):
) )
traceback.print_exc() traceback.print_exc()
break break
# 仅在连接异常时关闭 # 连接异常时关闭WebSocket
if not session_finished and self.ws: if self.ws:
try: try:
await self.ws.close() await self.ws.close()
except: except:
@@ -513,7 +517,7 @@ class TTSProvider(TTSProviderBase):
def read_res_content(self, res: bytes, offset: int): def read_res_content(self, res: bytes, offset: int):
content_size = int.from_bytes(res[offset : offset + 4], "big", signed=True) content_size = int.from_bytes(res[offset : offset + 4], "big", signed=True)
offset += 4 offset += 4
content = str(res[offset : offset + content_size]) content = res[offset : offset + content_size].decode('utf-8')
offset += content_size offset += content_size
return content, offset return content, offset