diff --git a/audio_processes.py b/audio_processes.py index 2df00bb..0172f69 100644 --- a/audio_processes.py +++ b/audio_processes.py @@ -522,12 +522,14 @@ class OutputProcess: """输出进程 - 借鉴 recorder.py 的优秀架构""" def __init__(self, audio_queue: mp.Queue, config: Dict[str, Any] = None, event_queue: mp.Queue = None): + print("🔊 OutputProcess __init__ 开始执行...") self.audio_queue = audio_queue # 主进程 → 输出进程(统一音频播放队列) self.event_queue = event_queue # 输出进程 → 主进程(事件通知) self.config = config or self._get_default_config() # 初始化日志记录器 self.logger = ProcessLogger("OutputProcess") + print("🔊 OutputProcess 基本初始化完成") # 音频播放参数 - 完全借鉴 recorder.py 的优化参数 self.FORMAT = pyaudio.paInt16 @@ -542,7 +544,9 @@ class OutputProcess: self.total_chunks_played = 0 self.total_audio_size = 0 self.last_playback_time = 0 # 最后播放时间戳 - self.playback_cooldown_period = 1.5 # 播放冷却时间(秒)- 防止回声 + self.playback_cooldown_period = 0.1 # 播放冷却时间(秒)- 防止回声,减少到0.1秒 + self.playback_completed = False # 播放完成标志 + self.end_signal_received = False # 结束信号接收标志 # 智能缓冲系统 - 借鉴 recorder.py 的缓冲策略 self.preload_buffer = [] # 预加载缓冲区 @@ -580,13 +584,24 @@ class OutputProcess: self.tts_speaker = "zh_female_wanqudashu_moon_bigtts" # 启动工作线程 - 先启动播放线程,再启动TTS线程 - self._start_playback_worker() - self._start_tts_worker() + print("🔊 准备启动播放工作线程...") + try: + self._start_playback_worker() + print("🔊 播放工作线程已启动,准备启动TTS工作线程...") + self._start_tts_worker() + print("🔊 TTS工作线程已启动") + except Exception as e: + print(f"❌ 工作线程启动失败: {e}") + import traceback + traceback.print_exc() def _start_playback_worker(self): """启动播放工作线程 - 借鉴 recorder.py 的播放线程模式""" + print("🔊 创建播放工作线程...") self.playback_worker_thread = threading.Thread(target=self._playback_worker, daemon=True) + print("🔊 启动播放工作线程...") self.playback_worker_thread.start() + print("🔊 播放工作线程已启动") self.logger.info("播放工作线程已启动") def _playback_worker(self): @@ -597,14 +612,20 @@ class OutputProcess: max_wait_time = 10 # 最多等待10秒 wait_start_time = time.time() + print(f"🔊 播放工作线程等待音频设备就绪... audio={self.audio is not None}, running={self.running}") + while (self.audio is None or not self.running) and (time.time() - wait_start_time) < max_wait_time: time.sleep(0.1) + if (time.time() - wait_start_time) % 1 < 0.1: # 每秒打印一次状态 + print(f"🔊 仍在等待音频设备就绪... audio={self.audio is not None}, running={self.running}") if self.audio is None: print("❌ 音频设备未就绪,播放工作线程退出") self.logger.error("音频设备未就绪,播放工作线程退出") return + print(f"✅ 音频设备已就绪,耗时: {time.time() - wait_start_time:.1f}秒") + print("🔊 音频设备已就绪,开始创建播放流") # 创建音频播放流 @@ -643,27 +664,55 @@ class OutputProcess: if in_cooldown: # 在冷却期内,跳过播放 - print(f"🔊 播放冷却中,跳过播放 ({time_since_last_play:.1f}s < {self.playback_cooldown_period}s)") + if chunks_played == 0: # 只在第一次遇到冷却期时打印 + print(f"🔊 播放冷却中,跳过播放 ({time_since_last_play:.1f}s < {self.playback_cooldown_period}s)") self.logger.debug(f"播放冷却中,跳过播放 ({time_since_last_play:.1f}s < {self.playback_cooldown_period}s)") continue # 播放音频块 + if chunks_played == 0: # 只在第一次播放时打印详细信息 + print(f"🔊 开始播放音频块 {chunks_played + 1}") + print(f"🔍 播放工作线程检查: 音频块大小={len(audio_chunk)}字节, " + f"冷却期检查={in_cooldown}, 距离上次播放={time_since_last_play:.2f}s, " + f"冷却阈值={self.playback_cooldown_period}s") + + # 标记正在播放 self.currently_playing = True - self.last_playback_time = current_time # 更新最后播放时间 + # 如果是第一次播放,不设置冷却期 + if chunks_played == 0: + self.last_playback_time = 0 # 第一次播放不触发冷却期 + else: + self.last_playback_time = current_time # 更新最后播放时间 + + # 播放音频(同步阻塞,直到播放完成) playback_stream.write(audio_chunk) chunks_played += 1 total_size += len(audio_chunk) # 减少进度显示频率 - if chunks_played % 10 == 0: + if chunks_played % 10 == 0 or chunks_played <= 3: progress = f"🔊 播放工作: {chunks_played} 块 | {total_size / 1024:.1f} KB" print(f"\r{progress}", end='', flush=True) + + # 播放完成后更新状态 + self.currently_playing = False + + # 如果这是最后一个音频块,主动检查播放完成 + if (len(self.playback_buffer) == 0 and + len(self.preload_buffer) == 0 and + self.tts_task_queue.qsize() == 0 and + not self.playback_completed): # 防止重复设置 + print(f"🔊 播放工作线程:播放完成,设置播放完成标志") + print(f"🔊 播放工作线程:播放缓冲={len(self.playback_buffer)}, 预加载={len(self.preload_buffer)}, TTS队列={self.tts_task_queue.qsize()}") + self.playback_completed = True + print(f"🔊 播放工作线程:playback_completed标志已设置为{self.playback_completed}") + # 不在这里直接调用_finish_playback,让主处理循环处理 else: self.currently_playing = False else: - # 缓冲区为空,检查是否还在接收数据 + # 缓冲区为空,短暂休眠,减少CPU占用 self.currently_playing = False - time.sleep(0.01) # 短暂休眠,减少CPU占用 + time.sleep(0.01) continue except Exception as e: @@ -712,7 +761,7 @@ class OutputProcess: tts_active = not self.tts_task_queue.empty() playback_active = self.currently_playing or len(self.playback_buffer) > 0 - # 如果设备不健康、正在播放、TTS生成中、或在冷却期内,显示状态并跳过音频处理 + # 如果设备不健康、正在播放、TTS生成中、或在冷却期内,显示状态但继续处理播放完成检测 if not self.audio_device_healthy or tts_active or playback_active or in_cooldown: # 显示播放状态 tts_queue_size = self.tts_task_queue.qsize() @@ -730,6 +779,11 @@ class OutputProcess: status = f"🔊 {playing_status}... TTS: {tts_queue_size} 播放: {queue_size}" print(f"\r{status}", end='', flush=True) + + # 关键修复:即使正在播放,也要检查播放完成 + if self.end_signal_received: + self._check_playback_completion() + time.sleep(0.1) # 播放时增加延迟减少CPU使用 continue @@ -747,7 +801,11 @@ class OutputProcess: # 4. 显示播放进度 self._show_progress() - # 5. 借鉴 recorder.py: 根据播放状态调整休眠时间,优化性能 + # 5. 主动检查播放完成(无论什么状态都要检查) + if self.end_signal_received: + self._check_playback_completion() + + # 6. 借鉴 recorder.py: 根据播放状态调整休眠时间,优化性能 if self.is_playing and (self.playback_buffer or self.preload_buffer): time.sleep(0.005) # 播放时极短休眠,提高响应性 else: @@ -766,8 +824,9 @@ class OutputProcess: def _setup_audio(self): """设置音频输出设备""" try: + print("🔊 开始初始化音频设备...") self.audio = pyaudio.PyAudio() - print("🔊 PyAudio实例已创建") + print(f"🔊 PyAudio实例已创建: {self.audio}") # 主进程不需要创建输出流,由播放工作线程负责 # 这里只创建PyAudio实例供播放工作线程使用 @@ -798,22 +857,32 @@ class OutputProcess: if audio_data is None: # 结束信号处理 + if end_signal_received: + print(f"📥 输出进程已收到过结束信号,忽略重复信号") + continue + print(f"📥 输出进程收到结束信号") end_signal_received = True + self.end_signal_received = True # 重置完成事件标记 self.completion_sent = False + # 重置播放完成标志 + self.playback_completed = False + print(f"📥 已重置所有播放完成相关标志") # 检查是否应该立即结束 tts_queue_size = self.tts_task_queue.qsize() playback_queue_size = len(self.playback_buffer) + len(self.preload_buffer) if tts_queue_size == 0 and playback_queue_size == 0 and not self.currently_playing: - print(f"📥 所有队列已清空且未在播放,处理结束信号") + # 双重确认机制:所有队列已清空且播放工作线程已停止 + print(f"📥 双重确认通过:TTS队列={tts_queue_size}, 播放缓冲={len(self.playback_buffer)}, 预加载={len(self.preload_buffer)}, 正在播放={self.currently_playing}") + print(f"📥 所有音频已播放完成,处理结束信号") self._finish_playback() return else: - print(f"📥 延迟处理结束信号 - TTS队列: {tts_queue_size}, 播放缓冲: {playback_queue_size}") + print(f"📥 延迟处理结束信号 - TTS队列: {tts_queue_size}, 播放缓冲: {playback_queue_size}, 正在播放: {self.currently_playing}") # 重新放回队列,稍后重试 self.audio_queue.put(None) time.sleep(0.05) @@ -835,17 +904,37 @@ class OutputProcess: print(f"📥 输出进程收到音频数据: {len(audio_data)} 字节") # 直接添加到预加载缓冲区 + print(f"🔍 添加音频到预加载缓冲区: 音频大小={len(audio_data)}字节, " + f"添加前预加载缓冲区大小={len(self.preload_buffer)}, " + f"添加前播放缓冲区大小={len(self.playback_buffer)}, " + f"is_playing={self.is_playing}") self.preload_buffer.append(audio_data) + print(f"🔍 添加后预加载缓冲区大小={len(self.preload_buffer)}") - # 检查是否应该开始播放 - if (not self.is_playing and - len(self.preload_buffer) >= self.preload_size): - # 将预加载的数据移到播放缓冲区 + # 检查是否应该开始播放或补充播放缓冲区 + if not self.is_playing and len(self.preload_buffer) >= self.preload_size: + # 首次启动播放 self.playback_buffer.extend(self.preload_buffer) self.preload_buffer.clear() self.is_playing = True - self.last_playback_time = time.time() + self.last_playback_time = 0 # 重置播放时间,避免立即触发冷却期 print(f"🎵 开始播放音频(预加载完成),播放缓冲区大小: {len(self.playback_buffer)}") + print(f"🔍 已重置last_playback_time,避免立即触发冷却期") + elif self.is_playing and len(self.playback_buffer) < 3 and len(self.preload_buffer) > 0: + # 正在播放时,保持播放缓冲区有足够的数据 + transfer_count = min(2, len(self.preload_buffer)) # 每次转移2个块 + for _ in range(transfer_count): + if self.preload_buffer: + self.playback_buffer.append(self.preload_buffer.pop(0)) + print(f"🔍 播放中补充数据: 转移{transfer_count}个块,播放缓冲区={len(self.playback_buffer)}, 预加载={len(self.preload_buffer)}") + elif end_signal_received and not self.is_playing and len(self.playback_buffer) == 0 and len(self.preload_buffer) > 0: + # 关键修复:收到结束信号后,如果播放缓冲区为空但预加载缓冲区有数据,强制转移 + print(f"🔍 结束信号模式下强制转移数据: 预加载缓冲区有 {len(self.preload_buffer)} 个数据块") + self.playback_buffer.extend(self.preload_buffer) + self.preload_buffer.clear() + self.is_playing = True + self.last_playback_time = 0 + print(f"🎵 强制开始播放音频,播放缓冲区大小: {len(self.playback_buffer)}") else: print(f"📥 输出进程收到未知类型数据: {type(audio_data)}") @@ -856,24 +945,67 @@ class OutputProcess: tts_queue_size = self.tts_task_queue.qsize() playback_queue_size = len(self.playback_buffer) + len(self.preload_buffer) - if tts_queue_size == 0 and playback_queue_size == 0 and not self.currently_playing: - print(f"📥 播放完成,所有工作已完成") - self._finish_playback() - return + print(f"📥 队列空时检查播放完成: TTS={tts_queue_size}, 播放缓冲={len(self.playback_buffer)}, 预加载={len(self.preload_buffer)}, 正在播放={self.currently_playing}") + + if tts_queue_size == 0 and playback_queue_size == 0: + # 三重确认机制:所有缓冲区都空了,检查播放状态 + if not self.currently_playing: + print(f"📥 三重确认通过:TTS队列=0, 播放缓冲=0, 预加载=0, 播放状态=False") + print(f"📥 调用 _finish_playback() 前,completion_sent={self.completion_sent}") + self._finish_playback() + print(f"📥 调用 _finish_playback() 后,completion_sent={self.completion_sent}") + return + else: + # 播放工作线程可能还在播放最后一个块,等待一下 + print(f"📥 等待播放工作线程完成...") + time.sleep(0.3) # 增加等待时间,确保播放完成 + if not self.currently_playing: + print(f"📥 三重确认通过(等待后):TTS队列=0, 播放缓冲=0, 预加载=0, 播放状态=False") + print(f"📥 调用 _finish_playback() 前,completion_sent={self.completion_sent}") + self._finish_playback() + print(f"📥 调用 _finish_playback() 后,completion_sent={self.completion_sent}") + return + else: + print(f"📥 播放工作线程仍在播放,继续等待...") + elif tts_queue_size == 0 and playback_queue_size > 0: + # 还有数据要播放,继续等待 + print(f"📥 还有 {playback_queue_size} 个音频块待播放,等待播放完成") + time.sleep(0.2) # 增加等待时间 + elif tts_queue_size == 0 and len(self.playback_buffer) == 0 and len(self.preload_buffer) > 0: + # 关键修复:播放缓冲区为空但预加载缓冲区还有数据,需要转移数据 + print(f"📥 播放缓冲区为空但预加载缓冲区有 {len(self.preload_buffer)} 个数据块,转移数据到播放缓冲区") + transfer_count = min(len(self.preload_buffer), 3) # 一次转移最多3个块 + for _ in range(transfer_count): + if self.preload_buffer: + self.playback_buffer.append(self.preload_buffer.pop(0)) + print(f"📥 已转移 {transfer_count} 个数据块到播放缓冲区") + time.sleep(0.2) # 增加等待时间 - # 检查是否应该将预加载缓冲区的数据移到播放缓冲区 - if (not self.is_playing and - len(self.preload_buffer) >= self.min_buffer_size): + # 检查是否应该补充播放缓冲区的数据 + if not self.is_playing and len(self.preload_buffer) >= self.min_buffer_size: + # 首次启动播放(最小缓冲区模式) self.playback_buffer.extend(self.preload_buffer) self.preload_buffer.clear() self.is_playing = True - self.last_playback_time = time.time() + self.last_playback_time = 0 # 重置播放时间,避免立即触发冷却期 print(f"🎵 开始播放音频(最小缓冲区满足)") + print(f"🔍 已重置last_playback_time,避免立即触发冷却期") + elif self.is_playing and len(self.playback_buffer) < 2 and len(self.preload_buffer) > 0: + # 正在播放时补充数据,避免播放缓冲区耗尽 + transfer_count = min(1, len(self.preload_buffer)) # 每次转移1个块 + for _ in range(transfer_count): + if self.preload_buffer: + self.playback_buffer.append(self.preload_buffer.pop(0)) + print(f"🔍 队列空时补充数据: 转移{transfer_count}个块,播放缓冲区={len(self.playback_buffer)}, 预加载={len(self.preload_buffer)}") # 退出循环,避免过度占用CPU if processed_count > 0: break else: + # 关键修复:即使没有处理数据,也要检查播放完成 + if self.end_signal_received: + self._check_playback_completion() + time.sleep(0.01) except Exception as e: @@ -920,15 +1052,18 @@ class OutputProcess: """完成播放 - 借鉴 recorder.py 的优雅完成机制""" # 防止重复发送完成事件 if self.completion_sent: + print("📡 输出进程:完成事件已发送过,跳过重复发送") return + print("📡 输出进程:开始执行播放完成逻辑") + self.is_playing = False self.playback_buffer.clear() self.preload_buffer.clear() self.last_playback_time = 0 # 通知主进程播放完成 - if self.event_queue and not self.completion_sent: + if self.event_queue: try: # 发送播放完成事件到主进程 completion_event = ProcessEvent( @@ -941,13 +1076,65 @@ class OutputProcess: print("📡 输出进程:已发送播放完成事件到主进程") self.completion_sent = True except Exception as e: + print(f"❌ 输出进程:发送播放完成事件失败: {e}") self.logger.error(f"发送播放完成事件失败: {e}") else: - if not self.event_queue: - print("⚠️ 输出进程:未设置事件队列,无法通知主进程播放完成") + print("⚠️ 输出进程:未设置事件队列,无法通知主进程播放完成") # 额外等待确保音频设备完全停止 time.sleep(0.5) + print("📡 输出进程:播放完成逻辑执行完毕") + + def _check_playback_completion(self): + """检查播放完成状态 - 独立的播放完成检测方法""" + if not self.end_signal_received: + return + + tts_queue_size = self.tts_task_queue.qsize() + playback_queue_size = len(self.playback_buffer) + len(self.preload_buffer) + + print(f"🔍 播放完成检查: TTS队列={tts_queue_size}, 播放缓冲={len(self.playback_buffer)}, 预加载={len(self.preload_buffer)}, 正在播放={self.currently_playing}, 播放完成标志={self.playback_completed}") + + # 检查条件1: 播放完成标志被设置 + if self.playback_completed: + print(f"✅ 检测到播放完成标志,触发播放完成") + print(f"📥 调用 _finish_playback() 前,completion_sent={self.completion_sent}") + self._finish_playback() + print(f"📥 调用 _finish_playback() 后,completion_sent={self.completion_sent}") + # 重要:重置播放完成标志,防止重复触发 + self.playback_completed = False + print(f"📥 已重置播放完成标志,防止重复触发") + return + + # 检查条件2: 所有队列为空且没有在播放 + if tts_queue_size == 0 and playback_queue_size == 0 and not self.currently_playing: + print(f"✅ 所有队列已清空且播放器空闲,触发播放完成") + print(f"📥 调用 _finish_playback() 前,completion_sent={self.completion_sent}") + self._finish_playback() + print(f"📥 调用 _finish_playback() 后,completion_sent={self.completion_sent}") + # 重要:重置结束信号标志,防止重复触发 + self.end_signal_received = False + print(f"📥 已重置结束信号标志,防止重复触发") + return + + # 检查条件3: 所有队列为空但播放器还在播放(最后一个音频块) + if tts_queue_size == 0 and playback_queue_size == 0 and self.currently_playing: + print(f"⏳ 等待最后一个音频块播放完成...") + time.sleep(0.3) + if not self.currently_playing: + print(f"✅ 最后一个音频块播放完成,触发播放完成") + print(f"📥 调用 _finish_playback() 前,completion_sent={self.completion_sent}") + self._finish_playback() + print(f"📥 调用 _finish_playback() 后,completion_sent={self.completion_sent}") + # 重要:重置结束信号标志,防止重复触发 + self.end_signal_received = False + print(f"📥 已重置结束信号标志,防止重复触发") + return + else: + print(f"⚠️ 最后一个音频块播放超时") + # 即使超时也要重置标志,防止重复检测 + self.end_signal_received = False + print(f"📥 已重置结束信号标志(超时情况)") def _cleanup(self): """清理资源""" diff --git a/control_system.py b/control_system.py index 9c1ea2f..beaede1 100644 --- a/control_system.py +++ b/control_system.py @@ -28,6 +28,19 @@ from audio_processes import ( RecordingState, ControlCommand, ProcessEvent ) +def output_process_target(audio_queue, config, event_queue): + """输出进程的目标函数 - 在子进程中创建OutputProcess实例""" + try: + print("🔊 输出进程目标函数开始执行...") + output_process = OutputProcess(audio_queue, config, event_queue) + print("🔊 OutputProcess实例创建成功,开始运行...") + output_process.run() + print("🔊 输出进程运行完成") + except Exception as e: + print(f"❌ 输出进程出错: {e}") + import traceback + traceback.print_exc() + class ControlSystem: """主控制系统""" @@ -213,11 +226,8 @@ class ControlSystem: } self.output_process = mp.Process( - target=OutputProcess( - self.output_audio_queue, - output_config, - self.output_event_queue # 传递事件队列 - ).run + target=output_process_target, + args=(self.output_audio_queue, output_config, self.output_event_queue) ) # 启动进程 @@ -312,6 +322,7 @@ class ControlSystem: if event.event_type == 'playback_complete': print("📡 主控制:收到播放完成事件") + print(f"📡 主控制:事件详情 - 类型: {event.event_type}, 元数据: {event.metadata}") self._handle_playback_complete(event) except queue.Empty: @@ -338,18 +349,31 @@ class ControlSystem: def _handle_playback_complete(self, event: ProcessEvent): """处理播放完成事件""" + print(f"📡 主控制:开始处理播放完成事件") + print(f"📡 主控制:当前状态 = {self.state.value}") + print(f"📡 主控制:事件元数据 = {event.metadata}") + # 标记播放完成 self.playback_complete = True + print(f"📡 主控制:已设置 playback_complete = True") # 更新统计 self.stats['total_conversations'] += 1 + print(f"📡 主控制:已更新统计,对话数 = {self.stats['total_conversations']}") # 切换到空闲状态 + old_state = self.state.value self.state = RecordingState.IDLE - print(f"🎯 状态:PLAYING → IDLE") + print(f"🎯 状态:{old_state} → IDLE") # 重新启用输入进程录音功能 - self.input_command_queue.put(ControlCommand('enable_recording')) + try: + self.input_command_queue.put(ControlCommand('enable_recording')) + print(f"📡 主控制:已发送 enable_recording 命令到输入进程") + except Exception as e: + print(f"❌ 主控制:发送 enable_recording 命令失败: {e}") + + print(f"📡 主控制:播放完成事件处理完成") def _process_audio_pipeline(self): """处理音频流水线:STT + LLM + TTS""" @@ -544,7 +568,6 @@ class ControlSystem: if serialization_method == 0b0001: # JSON payload_msg = json.loads(str(payload_msg, "utf-8")) - print(f"📋 解析后的JSON: {json.dumps(payload_msg, indent=2, ensure_ascii=False)}") result['payload_msg'] = payload_msg result['payload_size'] = payload_size @@ -721,7 +744,6 @@ class ControlSystem: return results else: print(f"❌ 未找到result字段,可用字段: {list(payload_msg.keys())}") - print(f"完整payload: {json.dumps(payload_msg, indent=2, ensure_ascii=False)}") else: print(f"❌ Payload不是字典类型: {type(payload_msg)}") else: @@ -854,11 +876,8 @@ class ControlSystem: if not chunk: continue - print(f"🔍 原始TTS响应块 {chunk_count + 1}: {chunk[:100]}...") - try: data = json.loads(chunk) - print(f"🔍 解析后的TTS块 {chunk_count + 1}: {data}") if data.get("code", 0) == 0 and "data" in data and data["data"]: chunk_audio = base64.b64decode(data["data"]) @@ -899,9 +918,41 @@ class ControlSystem: print(f"\n✅ TTS音频生成完成: {chunk_count} 块, {total_audio_size / 1024:.1f} KB") print(f"📊 队列大小变化: {queue_size_before} -> {self.output_audio_queue.qsize()}") - # 不再在这里发送结束信号,让输出进程自然播放完所有音频 - print(f"📦 TTS音频数据已全部发送,等待输出进程播放完成") + # 等待音频数据被输出进程完全处理 + print(f"📦 TTS音频数据已全部发送,等待输出进程处理...") + max_wait_time = 30 # 最多等待30秒 + wait_start_time = time.time() + last_queue_size = self.output_audio_queue.qsize() + + while time.time() - wait_start_time < max_wait_time: + current_queue_size = self.output_audio_queue.qsize() + + # 如果队列为空或队列大小不再变化,说明音频数据已被处理 + if current_queue_size == 0: + print(f"✅ 音频队列已清空,可以发送结束信号") + break + elif current_queue_size == last_queue_size: + # 队列大小不再变化,可能处理完成 + print(f"📦 队列大小稳定在 {current_queue_size},等待确认...") + time.sleep(1) + if self.output_audio_queue.qsize() == current_queue_size: + print(f"✅ 队列大小稳定,可以发送结束信号") + break + else: + print(f"📦 等待队列处理: {current_queue_size} 个项目待处理") + last_queue_size = current_queue_size + + time.sleep(0.5) + + # 发送结束信号,通知输出进程所有音频已发送完成 + print(f"📦 发送结束信号到输出进程") print(f"📊 音频队列当前大小: {self.output_audio_queue.qsize()}") + print(f"📦 注意:结束信号不会立即触发播放完成,会等待所有音频播放完成") + try: + self.output_audio_queue.put(None) + print(f"📡 已发送结束信号到输出进程") + except Exception as e: + print(f"❌ 发送结束信号失败: {e}") return chunk_count > 0 diff --git a/recorder.py b/recorder.py index b92f10a..82d1120 100644 --- a/recorder.py +++ b/recorder.py @@ -401,12 +401,16 @@ class EnergyBasedRecorder: if time_since_last >= self.tts_accumulation_time and len(self.tts_buffer) >= self.tts_buffer_min_size: return True - # 检查句子特征 - 长句子优先(调整为100字符,防止回声) - if len(sentence) > 100: # 超过100字符的长句子立即触发 + # 检查是否为完整句子(使用新的严格检测) + if self._is_complete_sentence(sentence): return True - # 中等长度句子(80-100字符)如果有结束标点也触发 - if len(sentence) > 80: + # 检查句子特征 - 长句子优先(50字符以上) + if len(sentence) > 50: # 超过50字符的句子立即触发 + return True + + # 中等长度句子(30-50字符)如果有结束标点也触发 + if len(sentence) > 30: end_punctuations = ['。', '!', '?', '.', '!', '?'] if any(sentence.strip().endswith(p) for p in end_punctuations): return True @@ -1808,12 +1812,18 @@ class EnergyBasedRecorder: if not text or len(text.strip()) == 0: return False + # 增加最小长度要求 - 至少10个字符才考虑作为完整句子 + if len(text.strip()) < 10: + return False + # 句子结束标点符号 sentence_endings = r'[。!?.!?]' # 检查是否以句子结束符结尾 if re.search(sentence_endings + r'\s*$', text): - return True + # 对于以结束符结尾的句子,要求至少15个字符 + if len(text.strip()) >= 15: + return True # 检查是否包含句子结束符(可能在句子中间) if re.search(sentence_endings, text): @@ -1821,18 +1831,25 @@ class EnergyBasedRecorder: remaining_text = re.split(sentence_endings, text, 1)[-1] if len(remaining_text.strip()) > 0: return False + # 对于包含结束符的句子,要求至少20个字符 + if len(text.strip()) >= 20: + return True + + # 对于较长的文本(超过50字符),即使没有结束符也可以考虑 + if len(text.strip()) >= 50: return True - # 对于较短的文本,如果包含常见完整句式模式 - common_patterns = [ - r'^[是的有没有来去在把被让叫请使].*[的得了吗呢吧啊呀]', - r'^(你好|谢谢|再见|是的|不是|好的|没问题)', - r'^[\u4e00-\u9fff]{2,4}[的得了]$' # 2-4个中文字+的/了/得 - ] - - for pattern in common_patterns: - if re.match(pattern, text): - return True + # 对于中等长度的文本,如果包含常见完整句式模式 + if len(text.strip()) >= 20: + common_patterns = [ + r'^[是的有没有来去在把被让叫请使].*[的得了吗呢吧啊呀]', + r'^(你好|谢谢|再见|是的|不是|好的|没问题)', + r'^[\u4e00-\u9fff]{4,8}[的得了]$' # 4-8个中文字+的/了/得 + ] + + for pattern in common_patterns: + if re.match(pattern, text): + return True return False