回声待处理

This commit is contained in:
朱潮 2025-09-20 20:19:02 +08:00
parent 5a7e39f5b5
commit d385333e0f

View File

@ -150,6 +150,7 @@ class EnergyBasedRecorder:
self.currently_playing = False # 当前是否正在播放 self.currently_playing = False # 当前是否正在播放
self.last_playback_time = 0 # 最后一次播放时间戳 self.last_playback_time = 0 # 最后一次播放时间戳
self.playback_cooldown_period = 1.5 # 播放冷却时间(秒) self.playback_cooldown_period = 1.5 # 播放冷却时间(秒)
self.audio_device_healthy = True # 音频设备健康状态
# 启动工作线程 # 启动工作线程
self._start_tts_worker() self._start_tts_worker()
@ -477,10 +478,24 @@ class EnergyBasedRecorder:
print(f"❌ 加载角色配置失败: {e}") print(f"❌ 加载角色配置失败: {e}")
return None return None
def _setup_audio(self): def _setup_audio(self, force_reset=False):
"""设置音频设备""" """设置音频设备"""
try: 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( self.stream = self.audio.open(
format=self.FORMAT, format=self.FORMAT,
channels=self.CHANNELS, channels=self.CHANNELS,
@ -489,8 +504,44 @@ class EnergyBasedRecorder:
frames_per_buffer=self.CHUNK_SIZE frames_per_buffer=self.CHUNK_SIZE
) )
print("✅ 音频设备初始化成功") print("✅ 音频设备初始化成功")
self.audio_device_healthy = True # 恢复设备健康状态
except Exception as e: except Exception as e:
print(f"❌ 音频设备初始化失败: {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): def calculate_energy(self, audio_data):
"""计算音频能量""" """计算音频能量"""
@ -589,7 +640,7 @@ class EnergyBasedRecorder:
def play_audio(self, filename): def play_audio(self, filename):
"""播放音频文件""" """播放音频文件"""
try: try:
print("🔇 准备播放,完全停止音频输入") print("🔇 准备播放,强制停止音频输入")
# 立即停止当前录音并清空所有缓冲区 # 立即停止当前录音并清空所有缓冲区
if self.recording: if self.recording:
@ -603,11 +654,8 @@ class EnergyBasedRecorder:
self.energy_history = [] self.energy_history = []
self.zcr_history = [] self.zcr_history = []
# 完全关闭输入流 # 强制关闭输入流,避免阻塞
if self.stream: self._force_close_audio_stream()
self.stream.stop_stream()
self.stream.close()
self.stream = None
# 设置播放状态 # 设置播放状态
self.is_playing = True self.is_playing = True
@ -663,13 +711,13 @@ class EnergyBasedRecorder:
# 等待播放完全结束 # 等待播放完全结束
time.sleep(0.3) time.sleep(0.3)
# 重新开启输入流 # 重新开启输入流(强制重置)
self._setup_audio() self._setup_audio(force_reset=True)
# 重置所有状态 # 重置所有状态
self.energy_history = [] self.energy_history = []
self.zcr_history = [] self.zcr_history = []
print("📡 音频输入已重新开启") print("📡 音频输入已重新开启(强制重置)")
def play_with_system_player(self, filename): def play_with_system_player(self, filename):
"""使用系统播放器播放音频""" """使用系统播放器播放音频"""
@ -835,11 +883,8 @@ class EnergyBasedRecorder:
self.energy_history = [] self.energy_history = []
self.zcr_history = [] self.zcr_history = []
# 关闭输入流 # 强制关闭输入流
if self.stream: self._force_close_audio_stream()
self.stream.stop_stream()
self.stream.close()
self.stream = None
self.is_playing = True self.is_playing = True
time.sleep(0.3) # 等待音频设备切换 time.sleep(0.3) # 等待音频设备切换
@ -987,7 +1032,7 @@ class EnergyBasedRecorder:
def play_audio_safe(self, filename, reopen_input=False): def play_audio_safe(self, filename, reopen_input=False):
"""安全的播放方式 - 使用系统播放器""" """安全的播放方式 - 使用系统播放器"""
try: try:
print("🔇 准备播放,完全停止音频输入") print("🔇 准备播放,强制停止音频输入")
# 立即停止当前录音并清空所有缓冲区 # 立即停止当前录音并清空所有缓冲区
if self.recording: if self.recording:
@ -1001,11 +1046,8 @@ class EnergyBasedRecorder:
self.energy_history = [] self.energy_history = []
self.zcr_history = [] self.zcr_history = []
# 完全关闭输入流 # 强制关闭输入流
if self.stream: self._force_close_audio_stream()
self.stream.stop_stream()
self.stream.close()
self.stream = None
# 设置播放状态 # 设置播放状态
self.is_playing = True self.is_playing = True
@ -1033,13 +1075,13 @@ class EnergyBasedRecorder:
# 只在需要时重新开启输入流 # 只在需要时重新开启输入流
if reopen_input: if reopen_input:
# 重新开启输入流 # 重新开启输入流(强制重置)
self._setup_audio() self._setup_audio(force_reset=True)
# 重置所有状态 # 重置所有状态
self.energy_history = [] self.energy_history = []
self.zcr_history = [] self.zcr_history = []
print("📡 音频输入已重新开启") print("📡 音频输入已重新开启(强制重置)")
def update_pre_record_buffer(self, audio_data): def update_pre_record_buffer(self, audio_data):
"""更新预录音缓冲区""" """更新预录音缓冲区"""
@ -1162,6 +1204,10 @@ class EnergyBasedRecorder:
print("🔄 音频播放完成,准备重新开启音频输入") print("🔄 音频播放完成,准备重新开启音频输入")
# 强制重置音频设备,确保完全关闭
print("🔄 强制重置音频设备...")
self._force_close_audio_stream()
self.recording = False self.recording = False
self.recorded_frames = [] self.recorded_frames = []
self.recording_start_time = None self.recording_start_time = None
@ -1231,7 +1277,7 @@ class EnergyBasedRecorder:
# 检查音频流是否可用 # 检查音频流是否可用
if self.stream is None: if self.stream is None:
print("\n❌ 音频流已断开,尝试重新连接...") print("\n❌ 音频流已断开,尝试重新连接...")
self._setup_audio() self._setup_audio(force_reset=True)
if self.stream is None: if self.stream is None:
print("❌ 音频流重连失败,等待...") print("❌ 音频流重连失败,等待...")
time.sleep(1) time.sleep(1)
@ -1248,16 +1294,18 @@ class EnergyBasedRecorder:
if len(data) == 0: if len(data) == 0:
continue continue
# 检查播放冷却期 - 防止回声 # 检查设备健康状态和播放冷却期 - 防止回声
current_time = time.time() current_time = time.time()
time_since_last_play = current_time - self.last_playback_time time_since_last_play = current_time - self.last_playback_time
in_cooldown = time_since_last_play < self.playback_cooldown_period 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() 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 cooldown_time = self.playback_cooldown_period - time_since_last_play
status = f"🔊 播放冷却中... {cooldown_time:.1f}s 队列: {queue_size}" status = f"🔊 播放冷却中... {cooldown_time:.1f}s 队列: {queue_size}"
else: else: