diff --git a/recorder.py b/recorder.py index 77aee99..665b11a 100644 --- a/recorder.py +++ b/recorder.py @@ -150,6 +150,7 @@ class EnergyBasedRecorder: self.currently_playing = False # 当前是否正在播放 self.last_playback_time = 0 # 最后一次播放时间戳 self.playback_cooldown_period = 1.5 # 播放冷却时间(秒) + self.audio_device_healthy = True # 音频设备健康状态 # 启动工作线程 self._start_tts_worker() @@ -477,10 +478,24 @@ class EnergyBasedRecorder: print(f"❌ 加载角色配置失败: {e}") return None - def _setup_audio(self): + def _setup_audio(self, force_reset=False): """设置音频设备""" try: - self.audio = pyaudio.PyAudio() + # 如果强制重置,先完全释放资源 + if force_reset: + self._force_close_audio_stream() + if self.audio: + try: + self.audio.terminate() + except: + pass + self.audio = None + + # 创建新的PyAudio实例 + if not self.audio: + self.audio = pyaudio.PyAudio() + + # 创建音频输入流 self.stream = self.audio.open( format=self.FORMAT, channels=self.CHANNELS, @@ -489,8 +504,44 @@ class EnergyBasedRecorder: frames_per_buffer=self.CHUNK_SIZE ) print("✅ 音频设备初始化成功") + self.audio_device_healthy = True # 恢复设备健康状态 except Exception as e: print(f"❌ 音频设备初始化失败: {e}") + self.audio_device_healthy = False + + def _force_close_audio_stream(self): + """强制关闭音频流,避免阻塞""" + if self.stream: + try: + # 尝试优雅关闭,设置超时 + import signal + + def timeout_handler(signum, frame): + raise TimeoutError("音频流关闭超时") + + # 设置5秒超时 + signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(5) + + try: + self.stream.stop_stream() + self.stream.close() + finally: + signal.alarm(0) # 取消超时 + + except TimeoutError: + print("⚠️ 音频流关闭超时,强制终止") + self.stream = None + except Exception as e: + print(f"⚠️ 优雅关闭音频流失败: {e}") + try: + # 强制关闭 + self.stream = None + except: + pass + finally: + self.stream = None + self.audio_device_healthy = False # 标记设备需要重置 def calculate_energy(self, audio_data): """计算音频能量""" @@ -589,7 +640,7 @@ class EnergyBasedRecorder: def play_audio(self, filename): """播放音频文件""" try: - print("🔇 准备播放,完全停止音频输入") + print("🔇 准备播放,强制停止音频输入") # 立即停止当前录音并清空所有缓冲区 if self.recording: @@ -603,11 +654,8 @@ class EnergyBasedRecorder: self.energy_history = [] self.zcr_history = [] - # 完全关闭输入流 - if self.stream: - self.stream.stop_stream() - self.stream.close() - self.stream = None + # 强制关闭输入流,避免阻塞 + self._force_close_audio_stream() # 设置播放状态 self.is_playing = True @@ -663,13 +711,13 @@ class EnergyBasedRecorder: # 等待播放完全结束 time.sleep(0.3) - # 重新开启输入流 - self._setup_audio() + # 重新开启输入流(强制重置) + self._setup_audio(force_reset=True) # 重置所有状态 self.energy_history = [] self.zcr_history = [] - print("📡 音频输入已重新开启") + print("📡 音频输入已重新开启(强制重置)") def play_with_system_player(self, filename): """使用系统播放器播放音频""" @@ -835,11 +883,8 @@ class EnergyBasedRecorder: self.energy_history = [] self.zcr_history = [] - # 关闭输入流 - if self.stream: - self.stream.stop_stream() - self.stream.close() - self.stream = None + # 强制关闭输入流 + self._force_close_audio_stream() self.is_playing = True time.sleep(0.3) # 等待音频设备切换 @@ -987,7 +1032,7 @@ class EnergyBasedRecorder: def play_audio_safe(self, filename, reopen_input=False): """安全的播放方式 - 使用系统播放器""" try: - print("🔇 准备播放,完全停止音频输入") + print("🔇 准备播放,强制停止音频输入") # 立即停止当前录音并清空所有缓冲区 if self.recording: @@ -1001,11 +1046,8 @@ class EnergyBasedRecorder: self.energy_history = [] self.zcr_history = [] - # 完全关闭输入流 - if self.stream: - self.stream.stop_stream() - self.stream.close() - self.stream = None + # 强制关闭输入流 + self._force_close_audio_stream() # 设置播放状态 self.is_playing = True @@ -1033,13 +1075,13 @@ class EnergyBasedRecorder: # 只在需要时重新开启输入流 if reopen_input: - # 重新开启输入流 - self._setup_audio() + # 重新开启输入流(强制重置) + self._setup_audio(force_reset=True) # 重置所有状态 self.energy_history = [] self.zcr_history = [] - print("📡 音频输入已重新开启") + print("📡 音频输入已重新开启(强制重置)") def update_pre_record_buffer(self, audio_data): """更新预录音缓冲区""" @@ -1162,6 +1204,10 @@ class EnergyBasedRecorder: print("🔄 音频播放完成,准备重新开启音频输入") + # 强制重置音频设备,确保完全关闭 + print("🔄 强制重置音频设备...") + self._force_close_audio_stream() + self.recording = False self.recorded_frames = [] self.recording_start_time = None @@ -1231,7 +1277,7 @@ class EnergyBasedRecorder: # 检查音频流是否可用 if self.stream is None: print("\n❌ 音频流已断开,尝试重新连接...") - self._setup_audio() + self._setup_audio(force_reset=True) if self.stream is None: print("❌ 音频流重连失败,等待...") time.sleep(1) @@ -1248,16 +1294,18 @@ class EnergyBasedRecorder: if len(data) == 0: continue - # 检查播放冷却期 - 防止回声 + # 检查设备健康状态和播放冷却期 - 防止回声 current_time = time.time() time_since_last_play = current_time - self.last_playback_time in_cooldown = time_since_last_play < self.playback_cooldown_period - # 如果正在播放、播放队列不为空、或在冷却期内,完全跳过音频处理 - if self.is_playing or self.currently_playing or not self.audio_playback_queue.empty() or in_cooldown: + # 如果设备不健康、正在播放、播放队列不为空、或在冷却期内,完全跳过音频处理 + if not self.audio_device_healthy or self.is_playing or self.currently_playing or not self.audio_playback_queue.empty() or in_cooldown: # 显示播放状态 queue_size = self.audio_playback_queue.qsize() - if in_cooldown: + if not self.audio_device_healthy: + status = f"🔧 设备重置中... 队列: {queue_size}" + elif in_cooldown: cooldown_time = self.playback_cooldown_period - time_since_last_play status = f"🔊 播放冷却中... {cooldown_time:.1f}s 队列: {queue_size}" else: