From 69cac9d40ab0e92533ff952c3d5e0ff7fcd32a66 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Thu, 3 Jul 2025 09:16:06 +0800 Subject: [PATCH 1/7] =?UTF-8?q?update:=20TTS=E5=A4=8D=E7=94=A8=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=EF=BC=8CVAD=E5=8F=8C=E9=98=88=E5=80=BC=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/config.yaml | 1 + main/xiaozhi-server/core/connection.py | 6 ++++ .../providers/tts/huoshan_double_stream.py | 36 ++++++++++++++----- .../core/providers/vad/silero.py | 28 +++++++++------ 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 5a43c995..a7a94658 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -325,6 +325,7 @@ VAD: SileroVAD: type: silero threshold: 0.5 + threshold_low: 0.3 model_dir: models/snakers4_silero-vad min_silence_duration_ms: 200 # 如果说话停顿比较长,可以把这个值设置大一些 diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 9d1949c4..375bd117 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -17,6 +17,7 @@ from core.utils.util import ( filter_sensitive_info, ) from typing import Dict, Any +from collections import deque from core.utils.modules_initialize import ( initialize_modules, initialize_tts, @@ -112,6 +113,8 @@ class ConnectionHandler: self.client_have_voice = False self.last_activity_time = 0.0 # 统一的活动时间戳(毫秒) self.client_voice_stop = False + self.client_voice_window = deque(maxlen=5) + self.last_is_voice = False # asr相关变量 # 因为实际部署时可能会用到公共的本地ASR,不能把变量暴露给公共ASR @@ -858,6 +861,9 @@ class ConnectionHandler: elif self.websocket: await self.websocket.close() + if self.tts: + await self.tts.close() + # 最后关闭线程池(避免阻塞) if self.executor: self.executor.shutdown(wait=False) diff --git a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py index 1ebaf15a..3fe6294e 100644 --- a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py +++ b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py @@ -1,4 +1,3 @@ -import os import uuid import json import queue @@ -10,7 +9,6 @@ from config.logger import setup_logging from core.utils import opus_encoder_utils from core.utils.util import check_model_key from core.providers.tts.base import TTSProviderBase -from core.handle.abortHandle import handleAbortMessage from core.providers.tts.dto.dto import SentenceType, ContentType, InterfaceType from asyncio import Task @@ -175,6 +173,9 @@ class TTSProvider(TTSProviderBase): async def _ensure_connection(self): """建立新的WebSocket连接""" try: + if self.ws: + logger.bind(tag=TAG).info(f"使用已有链接...") + return self.ws logger.bind(tag=TAG).info("开始建立新连接...") ws_header = { "X-Api-App-Key": self.appId, @@ -368,9 +369,17 @@ class TTSProvider(TTSProviderBase): logger.bind(tag=TAG).info("会话结束请求已发送") # 等待监听任务完成 - if hasattr(self, "_monitor_task"): + if hasattr(self, "_monitor_task") and self._monitor_task is not None: try: - await self._monitor_task + # 设置4秒超时等待监听任务结束 + await asyncio.wait_for(self._monitor_task, timeout=4) + except asyncio.TimeoutError: + logger.bind(tag=TAG).warning("等待监听任务超时,强制取消") + self._monitor_task.cancel() + try: + await self._monitor_task + except asyncio.CancelledError: + pass except Exception as e: logger.bind(tag=TAG).error( f"等待监听任务完成时发生错误: {str(e)}" @@ -378,8 +387,6 @@ class TTSProvider(TTSProviderBase): finally: self._monitor_task = None - # 关闭连接 - await self.close() except Exception as e: logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}") # 确保清理资源 @@ -400,6 +407,15 @@ class TTSProvider(TTSProviderBase): async def close(self): """资源清理方法""" + # 取消监听任务 + if self._monitor_task: + try: + self._monitor_task.cancel() + await self._monitor_task + except asyncio.CancelledError: + pass + self._monitor_task = None + if self.ws: try: await self.ws.close() @@ -413,6 +429,7 @@ class TTSProvider(TTSProviderBase): is_first_sentence = True first_sentence_segment_count = 0 # 添加计数器 try: + session_finished = False # 标记会话是否正常结束 while not self.conn.stop_event.is_set(): try: # 确保 `recv()` 运行在同一个 event loop @@ -466,6 +483,7 @@ class TTSProvider(TTSProviderBase): elif res.optional.event == EVENT_SessionFinished: logger.bind(tag=TAG).debug(f"会话结束~~") self._process_before_stop_play_files() + session_finished = True break except websockets.ConnectionClosed: logger.bind(tag=TAG).warning("WebSocket连接已关闭") @@ -476,15 +494,15 @@ class TTSProvider(TTSProviderBase): ) traceback.print_exc() break - finally: - # 确保清理资源 - if self.ws: + # 仅在连接异常时才关闭 + if not session_finished and self.ws: try: await self.ws.close() except: pass self.ws = None # 监听任务退出时清理引用 + finally: self._monitor_task = None async def send_event( diff --git a/main/xiaozhi-server/core/providers/vad/silero.py b/main/xiaozhi-server/core/providers/vad/silero.py index 2c45ba8c..b516d8fb 100644 --- a/main/xiaozhi-server/core/providers/vad/silero.py +++ b/main/xiaozhi-server/core/providers/vad/silero.py @@ -23,22 +23,24 @@ class VADProvider(VADProviderBase): # 处理空字符串的情况 threshold = config.get("threshold", "0.5") + threshold_low = config.get("threshold_low", "0.2") min_silence_duration_ms = config.get("min_silence_duration_ms", "1000") self.vad_threshold = float(threshold) if threshold else 0.5 + self.vad_threshold_low = float(threshold_low) if threshold_low else 0.2 + self.silence_threshold_ms = ( int(min_silence_duration_ms) if min_silence_duration_ms else 1000 ) + # 至少要多少帧才算有语音 + self.frame_window_threshold = 3 + def is_vad(self, conn, opus_packet): try: pcm_frame = self.decoder.decode(opus_packet, 960) conn.client_audio_buffer.extend(pcm_frame) # 将新数据加入缓冲区 - # 确保帧计数器存在 - if not hasattr(conn, "client_voice_frame_count"): - conn.client_voice_frame_count = 0 - # 处理缓冲区中的完整帧(每次处理512采样点) client_have_voice = False while len(conn.client_audio_buffer) >= 512 * 2: @@ -54,15 +56,21 @@ class VADProvider(VADProviderBase): # 检测语音活动 with torch.no_grad(): speech_prob = self.model(audio_tensor, 16000).item() - is_voice = speech_prob >= self.vad_threshold - if is_voice: - conn.client_voice_frame_count += 1 + # 双阈值判断 + if speech_prob >= self.vad_threshold: + is_voice = True + elif speech_prob <= self.vad_threshold_low: + is_voice = False else: - conn.client_voice_frame_count = 0 + is_voice = conn.last_is_voice - # 只有连续4帧检测到语音才认为有语音 - client_have_voice = conn.client_voice_frame_count >= 4 + # 声音没低于最低值则延续前一个状态,判断为有声音 + conn.last_is_voice = is_voice + + # 更新滑动窗口 + conn.client_voice_window.append(is_voice) + client_have_voice = (conn.client_voice_window.count(True) >= self.frame_window_threshold) # 如果之前有声音,但本次没有声音,且与上次有声音的时间差已经超过了静默阈值,则认为已经说完一句话 if conn.client_have_voice and not client_have_voice: From 7e5a2b4549fbd22e9727deac192fdd31179fa575 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Fri, 4 Jul 2025 11:27:49 +0800 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20=E6=92=AD=E6=94=BE=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E5=BC=95=E5=AF=BC=E8=AF=8D=E5=8D=A1=E9=A1=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/tts/base.py | 6 ++---- .../core/providers/tts/huoshan_double_stream.py | 10 +++++++--- main/xiaozhi-server/core/providers/tts/linkerai.py | 10 +++++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/main/xiaozhi-server/core/providers/tts/base.py b/main/xiaozhi-server/core/providers/tts/base.py index 223cb807..d196b8f9 100644 --- a/main/xiaozhi-server/core/providers/tts/base.py +++ b/main/xiaozhi-server/core/providers/tts/base.py @@ -362,10 +362,8 @@ class TTSProviderBase(ABC): return audio_datas def _process_before_stop_play_files(self): - for tts_file, text in self.before_stop_play_files: - if tts_file and os.path.exists(tts_file): - audio_datas = self._process_audio_file(tts_file) - self.tts_audio_queue.put((SentenceType.MIDDLE, audio_datas, text)) + for audio_datas, text in self.before_stop_play_files: + self.tts_audio_queue.put((SentenceType.MIDDLE, audio_datas, text)) self.before_stop_play_files.clear() self.tts_audio_queue.put((SentenceType.LAST, [], None)) diff --git a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py index 3fe6294e..6f8fcca5 100644 --- a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py +++ b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py @@ -1,3 +1,4 @@ +import os import uuid import json import queue @@ -245,9 +246,12 @@ class TTSProvider(TTSProviderBase): logger.bind(tag=TAG).info( f"添加音频文件到待播放列表: {message.content_file}" ) - self.before_stop_play_files.append( - (message.content_file, message.content_detail) - ) + if message.content_file and os.path.exists(message.content_file): + # 先处理文件音频数据 + file_audio = self._process_audio_file(message.content_file) + self.before_stop_play_files.append( + (file_audio, message.content_detail) + ) if message.sentence_type == SentenceType.LAST: try: diff --git a/main/xiaozhi-server/core/providers/tts/linkerai.py b/main/xiaozhi-server/core/providers/tts/linkerai.py index ee108540..1046b972 100644 --- a/main/xiaozhi-server/core/providers/tts/linkerai.py +++ b/main/xiaozhi-server/core/providers/tts/linkerai.py @@ -1,3 +1,4 @@ +import os import queue import asyncio import traceback @@ -63,9 +64,12 @@ class TTSProvider(TTSProviderBase): logger.bind(tag=TAG).info( f"添加音频文件到待播放列表: {message.content_file}" ) - self.before_stop_play_files.append( - (message.content_file, message.content_detail) - ) + if message.content_file and os.path.exists(message.content_file): + # 先处理文件音频数据 + file_audio = self._process_audio_file(message.content_file) + self.before_stop_play_files.append( + (file_audio, message.content_detail) + ) if message.sentence_type == SentenceType.LAST: # 处理剩余的文本 From 8da9dc8a3f8f761515e6b96f365cf5106ee40229 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Mon, 7 Jul 2025 11:45:37 +0800 Subject: [PATCH 3/7] =?UTF-8?q?update:=E4=BC=98=E5=8C=96=E5=BC=95=E5=AF=BC?= =?UTF-8?q?=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/plugins_func/functions/play_music.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/xiaozhi-server/plugins_func/functions/play_music.py b/main/xiaozhi-server/plugins_func/functions/play_music.py index 6d184b6b..a6eb5822 100644 --- a/main/xiaozhi-server/plugins_func/functions/play_music.py +++ b/main/xiaozhi-server/plugins_func/functions/play_music.py @@ -181,10 +181,10 @@ def _get_random_play_prompt(song_name): f"正在为您播放,{clean_name}", f"请欣赏歌曲,{clean_name}", f"即将为您播放,{clean_name}", - f"为您带来,{clean_name}", - f"让我们聆听,{clean_name}", + f"现在为您带来,《{clean_name}》", + f"让我们一起聆听,《{clean_name}》", f"接下来请欣赏,{clean_name}", - f"为您献上,{clean_name}", + f"此刻为您献上,《{clean_name}》", ] # 直接使用random.choice,不设置seed return random.choice(prompts) From 44593ff3f38a807463e74b4fae2adaa39cc49975 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Tue, 8 Jul 2025 17:48:50 +0800 Subject: [PATCH 4/7] =?UTF-8?q?update:=20=E4=BC=98=E5=8C=96chat=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=B5=81=E7=A8=8B=20=E4=BC=98=E5=8C=96huoshan?= =?UTF-8?q?=E5=A4=84=E7=90=86=20=E4=BC=9A=E8=AF=9D=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 38 +++++---- .../core/handle/intentHandler.py | 19 ++++- .../xiaozhi-server/core/providers/tts/base.py | 14 ---- .../providers/tts/huoshan_double_stream.py | 78 ++++++++----------- .../plugins_func/functions/play_music.py | 35 ++++----- 5 files changed, 86 insertions(+), 98 deletions(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 375bd117..e0eb90bc 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -594,13 +594,24 @@ class ConnectionHandler: # 更新系统prompt至上下文 self.dialogue.update_system_message(self.prompt) - def chat(self, query, tool_call=False): + def chat(self, query, tool_call=False, depth=0): self.logger.bind(tag=TAG).info(f"大模型收到用户消息: {query}") self.llm_finish_task = False if not tool_call: self.dialogue.put(Message(role="user", content=query)) + # 为最顶层时新建会话ID和发送FIRST请求 + if depth == 0: + self.sentence_id = str(uuid.uuid4().hex) + self.tts.tts_text_queue.put( + TTSMessageDTO( + sentence_id=self.sentence_id, + sentence_type=SentenceType.FIRST, + content_type=ContentType.ACTION, + ) + ) + # Define intent functions functions = None if self.intent_type == "function_call" and hasattr(self, "func_handler"): @@ -616,8 +627,6 @@ class ConnectionHandler: ) memory_str = future.result() - self.sentence_id = str(uuid.uuid4().hex) - if self.intent_type == "function_call" and functions is not None: # 使用支持functions的streaming接口 llm_responses = self.llm.response_with_functions( @@ -640,7 +649,6 @@ class ConnectionHandler: function_id = None function_arguments = "" content_arguments = "" - text_index = 0 self.client_abort = False for response in llm_responses: if self.client_abort: @@ -670,14 +678,6 @@ class ConnectionHandler: if content is not None and len(content) > 0: if not tool_call_flag: response_message.append(content) - if text_index == 0: - self.tts.tts_text_queue.put( - TTSMessageDTO( - sentence_id=self.sentence_id, - sentence_type=SentenceType.FIRST, - content_type=ContentType.ACTION, - ) - ) self.tts.tts_text_queue.put( TTSMessageDTO( sentence_id=self.sentence_id, @@ -686,7 +686,6 @@ class ConnectionHandler: content_detail=content, ) ) - text_index += 1 # 处理function call if tool_call_flag: bHasError = False @@ -711,6 +710,11 @@ class ConnectionHandler: f"function call error: {content_arguments}" ) if not bHasError: + # 如需要大模型先处理一轮,添加相关处理后的日志情况 + if len(response_message) > 0: + self.dialogue.put( + Message(role="assistant", content="".join(response_message)) + ) response_message.clear() self.logger.bind(tag=TAG).debug( f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}" @@ -728,14 +732,14 @@ class ConnectionHandler: ), self.loop, ).result() - self._handle_function_result(result, function_call_data) + self._handle_function_result(result, function_call_data, depth=depth) # 存储对话内容 if len(response_message) > 0: self.dialogue.put( Message(role="assistant", content="".join(response_message)) ) - if text_index > 0: + if depth == 0: self.tts.tts_text_queue.put( TTSMessageDTO( sentence_id=self.sentence_id, @@ -750,7 +754,7 @@ class ConnectionHandler: return True - def _handle_function_result(self, result, function_call_data): + def _handle_function_result(self, result, function_call_data, depth): if result.action == Action.RESPONSE: # 直接回复前端 text = result.response self.tts.tts_one_sentence(self, ContentType.TEXT, content_detail=text) @@ -787,7 +791,7 @@ class ConnectionHandler: content=text, ) ) - self.chat(text, tool_call=True) + self.chat(text, tool_call=True, depth=depth + 1) elif result.action == Action.NOTFOUND or result.action == Action.ERROR: text = result.response if result.response else result.result self.tts.tts_one_sentence(self, ContentType.TEXT, content_detail=text) diff --git a/main/xiaozhi-server/core/handle/intentHandler.py b/main/xiaozhi-server/core/handle/intentHandler.py index 2d0baca4..a398195b 100644 --- a/main/xiaozhi-server/core/handle/intentHandler.py +++ b/main/xiaozhi-server/core/handle/intentHandler.py @@ -6,9 +6,8 @@ from core.handle.helloHandle import checkWakeupWords from core.utils.util import remove_punctuation_and_length from core.providers.tts.dto.dto import ContentType from core.utils.dialogue import Message -from core.providers.tools.device_mcp import call_mcp_tool from plugins_func.register import Action, ActionResponse -from loguru import logger +from core.providers.tts.dto.dto import TTSMessageDTO, SentenceType TAG = __name__ @@ -29,6 +28,8 @@ async def handle_user_intent(conn, text): intent_result = await analyze_intent_with_llm(conn, text) if not intent_result: return False + # 会话开始时生成sentence_id + conn.sentence_id = str(uuid.uuid4().hex) # 处理各种意图 return await process_intent_result(conn, intent_result, text) @@ -158,5 +159,19 @@ async def process_intent_result(conn, intent_result, original_text): def speak_txt(conn, text): + conn.tts.tts_text_queue.put( + TTSMessageDTO( + sentence_id=conn.sentence_id, + sentence_type=SentenceType.FIRST, + content_type=ContentType.ACTION, + ) + ) conn.tts.tts_one_sentence(conn, ContentType.TEXT, content_detail=text) + conn.tts.tts_text_queue.put( + TTSMessageDTO( + sentence_id=conn.sentence_id, + sentence_type=SentenceType.LAST, + content_type=ContentType.ACTION, + ) + ) conn.dialogue.put(Message(role="assistant", content=text)) diff --git a/main/xiaozhi-server/core/providers/tts/base.py b/main/xiaozhi-server/core/providers/tts/base.py index d196b8f9..c89b3247 100644 --- a/main/xiaozhi-server/core/providers/tts/base.py +++ b/main/xiaozhi-server/core/providers/tts/base.py @@ -161,13 +161,6 @@ class TTSProviderBase(ABC): else: sentence_id = str(uuid.uuid4()).replace("-", "") conn.sentence_id = sentence_id - self.tts_text_queue.put( - TTSMessageDTO( - sentence_id=sentence_id, - sentence_type=SentenceType.FIRST, - content_type=ContentType.ACTION, - ) - ) # 对于单句的文本,进行分段处理 segments = re.split(r"([。!?!?;;\n])", content_detail) for seg in segments: @@ -180,13 +173,6 @@ class TTSProviderBase(ABC): content_file=content_file, ) ) - self.tts_text_queue.put( - TTSMessageDTO( - sentence_id=sentence_id, - sentence_type=SentenceType.LAST, - content_type=ContentType.ACTION, - ) - ) async def open_audio_channels(self, conn): self.conn = conn diff --git a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py index 6f8fcca5..49643b85 100644 --- a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py +++ b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py @@ -202,6 +202,10 @@ class TTSProvider(TTSProviderBase): logger.bind(tag=TAG).debug( f"收到TTS任务|{message.sentence_type.name} | {message.content_type.name} | 会话ID: {self.conn.sentence_id}" ) + + if message.sentence_type == SentenceType.FIRST: + self.conn.client_abort = False + if self.conn.client_abort: logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程") continue @@ -300,24 +304,23 @@ class TTSProvider(TTSProviderBase): async def start_session(self, session_id): logger.bind(tag=TAG).info(f"开始会话~~{session_id}") try: - task = self._monitor_task + # 会话开始时检测上个会话的监听状态 if ( - task is not None - and isinstance(task, Task) - and not task.done() + self._monitor_task is not None + and isinstance(self._monitor_task, Task) + and not self._monitor_task.done() ): - logger.bind(tag=TAG).info("等待上一个监听任务结束...") - if self.ws is not None: - logger.bind(tag=TAG).info("强制关闭上一个WebSocket连接以唤醒监听任务...") - try: - await self.ws.close() - except Exception as e: - logger.bind(tag=TAG).warning(f"关闭上一个ws异常: {e}") - self.ws = None + logger.bind(tag=TAG).info("取消上一个监听任务...") + self._monitor_task.cancel() try: - await asyncio.wait_for(task, timeout=8) + # 等待任务取消完成 + await asyncio.wait_for(self._monitor_task, timeout=3) + except asyncio.CancelledError: + logger.bind(tag=TAG).info("监听任务已成功取消") + except asyncio.TimeoutError: + logger.bind(tag=TAG).warning("取消监听任务超时,可能仍在运行") except Exception as e: - logger.bind(tag=TAG).warning(f"等待监听任务异常: {e}") + logger.bind(tag=TAG).warning(f"取消监听任务异常: {e}") self._monitor_task = None # 建立新连接 await self._ensure_connection() @@ -341,19 +344,7 @@ class TTSProvider(TTSProviderBase): except Exception as e: logger.bind(tag=TAG).error(f"启动会话失败: {str(e)}") # 确保清理资源 - if hasattr(self, "_monitor_task"): - try: - self._monitor_task.cancel() - await self._monitor_task - except: - pass - self._monitor_task = None - if self.ws: - try: - await self.ws.close() - except: - pass - self.ws = None + await self.close() raise async def finish_session(self, session_id): @@ -379,11 +370,14 @@ class TTSProvider(TTSProviderBase): await asyncio.wait_for(self._monitor_task, timeout=4) except asyncio.TimeoutError: logger.bind(tag=TAG).warning("等待监听任务超时,强制取消") - self._monitor_task.cancel() - try: - await self._monitor_task - except asyncio.CancelledError: - pass + if not self._monitor_task.done(): + self._monitor_task.cancel() + try: + await self._monitor_task + except asyncio.CancelledError: + pass + except Exception as e: + logger.bind(tag=TAG).warning(f"监听任务取消时发生错误: {e}") except Exception as e: logger.bind(tag=TAG).error( f"等待监听任务完成时发生错误: {str(e)}" @@ -394,31 +388,21 @@ class TTSProvider(TTSProviderBase): except Exception as e: logger.bind(tag=TAG).error(f"关闭会话失败: {str(e)}") # 确保清理资源 - if hasattr(self, "_monitor_task"): - try: - self._monitor_task.cancel() - await self._monitor_task - except: - pass - self._monitor_task = None - if self.ws: - try: - await self.ws.close() - except: - pass - self.ws = None + await self.close() raise async def close(self): """资源清理方法""" # 取消监听任务 - if self._monitor_task: + if self._monitor_task and not self._monitor_task.done(): try: self._monitor_task.cancel() await self._monitor_task except asyncio.CancelledError: pass - self._monitor_task = None + except Exception as e: + logger.bind(tag=TAG).warning(f"关闭时取消监听任务错误: {e}") + self._monitor_task = None if self.ws: try: diff --git a/main/xiaozhi-server/plugins_func/functions/play_music.py b/main/xiaozhi-server/plugins_func/functions/play_music.py index a6eb5822..6451e526 100644 --- a/main/xiaozhi-server/plugins_func/functions/play_music.py +++ b/main/xiaozhi-server/plugins_func/functions/play_music.py @@ -1,13 +1,10 @@ -from config.logger import setup_logging import os import re import time import random -import asyncio import difflib import traceback from pathlib import Path -from core.utils import p3 from core.handle.sendAudioHandle import send_stt_message from plugins_func.register import register_function, ToolType, ActionResponse, Action from core.utils.dialogue import Message @@ -51,8 +48,8 @@ def play_music(conn, song_name: str): ) # 提交异步任务 - future = asyncio.run_coroutine_threadsafe( - handle_music_command(conn, music_intent), conn.loop + task = conn.loop.create_task( + handle_music_command(conn, music_intent) # 封装异步逻辑 ) # 非阻塞回调处理 @@ -63,7 +60,7 @@ def play_music(conn, song_name: str): except Exception as e: conn.logger.bind(tag=TAG).error(f"播放失败: {e}") - future.add_done_callback(handle_done) + task.add_done_callback(handle_done) return ActionResponse( action=Action.NONE, result="指令已接收", response="正在为您播放音乐" @@ -218,13 +215,14 @@ async def play_local_music(conn, specific_file=None): await send_stt_message(conn, text) conn.dialogue.put(Message(role="assistant", content=text)) - conn.tts.tts_text_queue.put( - TTSMessageDTO( - sentence_id=conn.sentence_id, - sentence_type=SentenceType.FIRST, - content_type=ContentType.ACTION, + if conn.intent_type == "intent_llm": + conn.tts.tts_text_queue.put( + TTSMessageDTO( + sentence_id=conn.sentence_id, + sentence_type=SentenceType.FIRST, + content_type=ContentType.ACTION, + ) ) - ) conn.tts.tts_text_queue.put( TTSMessageDTO( sentence_id=conn.sentence_id, @@ -241,13 +239,14 @@ async def play_local_music(conn, specific_file=None): content_file=music_path, ) ) - conn.tts.tts_text_queue.put( - TTSMessageDTO( - sentence_id=conn.sentence_id, - sentence_type=SentenceType.LAST, - content_type=ContentType.ACTION, + if conn.intent_type == "intent_llm": + conn.tts.tts_text_queue.put( + TTSMessageDTO( + sentence_id=conn.sentence_id, + sentence_type=SentenceType.LAST, + content_type=ContentType.ACTION, + ) ) - ) except Exception as e: conn.logger.bind(tag=TAG).error(f"播放音乐失败: {str(e)}") From 62e39dd0e248be0d89689709d3c0caaaf930a626 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Wed, 9 Jul 2025 11:18:26 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=E7=AD=89=E5=BE=85=E6=97=B6=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E5=B7=B2=E7=BB=8F=E5=AE=8C=E6=88=90=EF=BC=88=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=BANone=EF=BC=89=EF=BC=8C=E5=90=8E=E7=BB=AD?= =?UTF-8?q?=E5=AF=B9None=E9=94=99=E8=AF=AF=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../providers/tts/huoshan_double_stream.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py index 49643b85..e6e4d2fa 100644 --- a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py +++ b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py @@ -364,20 +364,9 @@ class TTSProvider(TTSProviderBase): logger.bind(tag=TAG).info("会话结束请求已发送") # 等待监听任务完成 - if hasattr(self, "_monitor_task") and self._monitor_task is not None: + if self._monitor_task: try: - # 设置4秒超时等待监听任务结束 - await asyncio.wait_for(self._monitor_task, timeout=4) - except asyncio.TimeoutError: - logger.bind(tag=TAG).warning("等待监听任务超时,强制取消") - if not self._monitor_task.done(): - self._monitor_task.cancel() - try: - await self._monitor_task - except asyncio.CancelledError: - pass - except Exception as e: - logger.bind(tag=TAG).warning(f"监听任务取消时发生错误: {e}") + await self._monitor_task except Exception as e: logger.bind(tag=TAG).error( f"等待监听任务完成时发生错误: {str(e)}" @@ -394,7 +383,7 @@ class TTSProvider(TTSProviderBase): async def close(self): """资源清理方法""" # 取消监听任务 - if self._monitor_task and not self._monitor_task.done(): + if self._monitor_task: try: self._monitor_task.cancel() await self._monitor_task @@ -402,7 +391,7 @@ class TTSProvider(TTSProviderBase): pass except Exception as e: logger.bind(tag=TAG).warning(f"关闭时取消监听任务错误: {e}") - self._monitor_task = None + self._monitor_task = None if self.ws: try: From 7f67459c6eb95e3f49d048e400f0b533468da2c7 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Wed, 9 Jul 2025 16:26:26 +0800 Subject: [PATCH 6/7] =?UTF-8?q?update:=E5=A2=9E=E5=8A=A0=E4=B9=A6=E5=90=8D?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/plugins_func/functions/play_music.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/xiaozhi-server/plugins_func/functions/play_music.py b/main/xiaozhi-server/plugins_func/functions/play_music.py index 6451e526..2cbc4018 100644 --- a/main/xiaozhi-server/plugins_func/functions/play_music.py +++ b/main/xiaozhi-server/plugins_func/functions/play_music.py @@ -175,12 +175,12 @@ def _get_random_play_prompt(song_name): # 移除文件扩展名 clean_name = os.path.splitext(song_name)[0] prompts = [ - f"正在为您播放,{clean_name}", - f"请欣赏歌曲,{clean_name}", - f"即将为您播放,{clean_name}", + f"正在为您播放,《{clean_name}》", + f"请欣赏歌曲,《{clean_name}》", + f"即将为您播放,《{clean_name}》", f"现在为您带来,《{clean_name}》", f"让我们一起聆听,《{clean_name}》", - f"接下来请欣赏,{clean_name}", + f"接下来请欣赏,《{clean_name}》", f"此刻为您献上,《{clean_name}》", ] # 直接使用random.choice,不设置seed From 2625133f407125b77bfa3d2c940154655b43e519 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Wed, 9 Jul 2025 17:04:42 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=E6=89=93=E6=96=AD=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=9C=AA=E9=87=8D=E7=BD=AE=20=E7=9B=91=E5=90=AC=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=97=B6=E6=9C=8D=E5=8A=A1=E7=AB=AF=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E8=BF=98=E5=9C=A8=E5=8F=91=E9=80=81=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=20=E6=AD=A4=E6=97=B6=E5=A4=8D=E7=94=A8=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E4=BC=9A=E6=8E=A5=E6=94=B6=E4=B8=8A=E4=B8=AA=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E7=9A=84=E6=AE=8B=E4=BD=99=20=E9=9C=80=E8=A6=81=E4=B8=A4?= =?UTF-8?q?=E8=80=85=E4=B8=80=E5=90=8C=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/tts/base.py | 2 ++ .../core/providers/tts/huoshan_double_stream.py | 15 +++------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/main/xiaozhi-server/core/providers/tts/base.py b/main/xiaozhi-server/core/providers/tts/base.py index c89b3247..9127ec12 100644 --- a/main/xiaozhi-server/core/providers/tts/base.py +++ b/main/xiaozhi-server/core/providers/tts/base.py @@ -195,6 +195,8 @@ class TTSProviderBase(ABC): while not self.conn.stop_event.is_set(): try: message = self.tts_text_queue.get(timeout=1) + if message.sentence_type == SentenceType.FIRST: + self.conn.client_abort = False if self.conn.client_abort: logger.bind(tag=TAG).info("收到打断信息,终止TTS文本处理线程") continue diff --git a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py index e6e4d2fa..0eb7c72d 100644 --- a/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py +++ b/main/xiaozhi-server/core/providers/tts/huoshan_double_stream.py @@ -310,18 +310,9 @@ class TTSProvider(TTSProviderBase): and isinstance(self._monitor_task, Task) and not self._monitor_task.done() ): - logger.bind(tag=TAG).info("取消上一个监听任务...") - self._monitor_task.cancel() - try: - # 等待任务取消完成 - await asyncio.wait_for(self._monitor_task, timeout=3) - except asyncio.CancelledError: - logger.bind(tag=TAG).info("监听任务已成功取消") - except asyncio.TimeoutError: - logger.bind(tag=TAG).warning("取消监听任务超时,可能仍在运行") - except Exception as e: - logger.bind(tag=TAG).warning(f"取消监听任务异常: {e}") - self._monitor_task = None + logger.bind(tag=TAG).info("检测到未完成的上个会话,关闭监听任务和连接...") + await self.close() + # 建立新连接 await self._ensure_connection()