缓存
This commit is contained in:
parent
9cf562e3be
commit
91a9043c4a
66
CACHED_AUDIO_TIMING_FIX.md
Normal file
66
CACHED_AUDIO_TIMING_FIX.md
Normal file
@ -0,0 +1,66 @@
|
||||
# 缓存音频播放时序修复总结
|
||||
|
||||
## 问题描述
|
||||
在打招呼读取缓存播放时,发现有时会立即触发播放完成,显示异常的时间差(49.292秒),导致缓存音频无法正常播放完整。
|
||||
|
||||
## 根本原因
|
||||
在 `_process_cached_audio` 方法中,缓存音频的处理存在时序问题:
|
||||
|
||||
1. **立即设置完成状态**:缓存音频被添加到预加载缓冲区后,立即设置 `all_audio_received = True`
|
||||
2. **时序变量未初始化**:`last_audio_chunk_time` 没有在缓存音频开始播放时正确设置
|
||||
3. **完成检测误判**:播放完成检测逻辑使用旧的或零值的 `last_audio_chunk_time`,计算出异常的时间差,误判播放已完成
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 修改 `_process_cached_audio` 方法
|
||||
- **延迟设置 `all_audio_received`**:不在音频添加到缓冲区时立即设置,而是等待实际开始播放时设置
|
||||
- **正确初始化时序变量**:在缓存音频开始播放时设置 `last_audio_chunk_time = time.time()`
|
||||
- **确保时序同步**:保证 `all_audio_received` 和 `last_audio_chunk_time` 在正确的时间点设置
|
||||
|
||||
### 2. 修改音频缓冲区转移逻辑
|
||||
在以下缓冲区转移场景中也添加了时序变量初始化:
|
||||
- 预加载缓冲区达到阈值时
|
||||
- 最小缓冲区模式启动时
|
||||
- 强制转移预加载缓冲区时
|
||||
|
||||
### 3. 关键修改点
|
||||
|
||||
#### 在 `_process_cached_audio` 中:
|
||||
```python
|
||||
# 修复前:立即设置all_audio_received
|
||||
self.all_audio_received = True
|
||||
|
||||
# 修复后:等待播放开始时设置
|
||||
self.last_audio_chunk_time = time.time() # 关键修复
|
||||
self.all_audio_received = True
|
||||
```
|
||||
|
||||
#### 在缓冲区转移逻辑中:
|
||||
```python
|
||||
# 在各种缓冲区转移场景中添加
|
||||
self.last_audio_chunk_time = time.time()
|
||||
print(f"🎵 设置last_audio_chunk_time = {self.last_audio_chunk_time}")
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
1. **解决立即完成问题**:缓存音频不再出现49.292秒的异常时间差
|
||||
2. **确保完整播放**:缓存音频能够正常播放完整时长
|
||||
3. **时序同步**:播放完成检测逻辑现在使用正确的时间戳
|
||||
4. **避免死锁**:即使在异常情况下,也能正确设置完成状态避免系统卡死
|
||||
|
||||
## 测试验证
|
||||
|
||||
创建了专门的测试脚本 `test_cached_timing_fix.py` 验证修复效果:
|
||||
- ✅ 关键时序变量存在且可访问
|
||||
- ✅ 时序变量可以正确设置和读取
|
||||
- ✅ 时间差计算正常(接近0秒而非49.292秒)
|
||||
- ✅ 异常时间差问题得到解决
|
||||
|
||||
## 影响范围
|
||||
|
||||
- **缓存音频播放**:修复了所有缓存音频的播放时序问题
|
||||
- **播放完成检测**:改进了播放完成检测的准确性
|
||||
- **系统稳定性**:提高了整个音频播放系统的稳定性
|
||||
|
||||
这个修复确保了缓存音频(如角色打招呼)能够正常播放完整,不会立即触发播放完成。
|
||||
@ -8,6 +8,7 @@
|
||||
"""
|
||||
|
||||
import base64
|
||||
import glob
|
||||
import gzip
|
||||
import json
|
||||
import multiprocessing as mp
|
||||
@ -72,6 +73,238 @@ def save_greeting_cache(character_name, audio_data):
|
||||
print(f"❌ 保存缓存音频失败: {e}")
|
||||
return False
|
||||
|
||||
# ========== 录音文件清理机制 ==========
|
||||
|
||||
def cleanup_recordings(retention_hours=24, dry_run=False):
|
||||
"""
|
||||
清理过期的录音文件
|
||||
|
||||
Args:
|
||||
retention_hours: 保留时间(小时),默认24小时
|
||||
dry_run: 是否只是模拟运行,不实际删除文件
|
||||
|
||||
Returns:
|
||||
dict: 清理结果统计
|
||||
"""
|
||||
try:
|
||||
# 计算截止时间
|
||||
cutoff_time = time.time() - (retention_hours * 3600)
|
||||
|
||||
# 查找所有录音文件
|
||||
recording_files = glob.glob("recording_*.wav")
|
||||
|
||||
stats = {
|
||||
'total_files': len(recording_files),
|
||||
'files_to_delete': 0,
|
||||
'files_deleted': 0,
|
||||
'total_size_mb': 0.0,
|
||||
'freed_space_mb': 0.0,
|
||||
'deleted_files': []
|
||||
}
|
||||
|
||||
if not recording_files:
|
||||
print(f"🧹 未找到录音文件")
|
||||
return stats
|
||||
|
||||
# 计算总大小
|
||||
for file_path in recording_files:
|
||||
try:
|
||||
file_size = os.path.getsize(file_path)
|
||||
stats['total_size_mb'] += file_size / (1024 * 1024)
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"🧹 开始清理录音文件(保留 {retention_hours} 小时内的文件)")
|
||||
print(f"📊 找到 {stats['total_files']} 个录音文件,总大小: {stats['total_size_mb']:.2f} MB")
|
||||
|
||||
if dry_run:
|
||||
print(f"🔍 模拟运行模式,不会实际删除文件")
|
||||
|
||||
# 检查每个文件
|
||||
for file_path in recording_files:
|
||||
try:
|
||||
# 获取文件修改时间
|
||||
file_mtime = os.path.getmtime(file_path)
|
||||
|
||||
if file_mtime < cutoff_time:
|
||||
# 文件已过期
|
||||
stats['files_to_delete'] += 1
|
||||
|
||||
# 获取文件大小
|
||||
file_size = os.path.getsize(file_path)
|
||||
stats['freed_space_mb'] += file_size / (1024 * 1024)
|
||||
|
||||
if not dry_run:
|
||||
# 实际删除文件
|
||||
os.remove(file_path)
|
||||
stats['files_deleted'] += 1
|
||||
stats['deleted_files'].append(os.path.basename(file_path))
|
||||
print(f"🗑️ 已删除: {file_path} ({file_size / 1024:.1f} KB)")
|
||||
else:
|
||||
print(f"🔍 标记删除: {file_path} ({file_size / 1024:.1f} KB)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 处理文件 {file_path} 时出错: {e}")
|
||||
continue
|
||||
|
||||
# 输出清理结果
|
||||
print(f"📊 清理完成:")
|
||||
print(f" - 总文件数: {stats['total_files']}")
|
||||
print(f" - 应删除文件数: {stats['files_to_delete']}")
|
||||
if not dry_run:
|
||||
print(f" - 实际删除文件数: {stats['files_deleted']}")
|
||||
print(f" - 释放空间: {stats['freed_space_mb']:.2f} MB")
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 清理录音文件时出错: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def cleanup_recordings_by_count(max_files=50, dry_run=False):
|
||||
"""
|
||||
按文件数量清理录音文件,保留最新的N个文件
|
||||
|
||||
Args:
|
||||
max_files: 最大保留文件数量,默认50个
|
||||
dry_run: 是否只是模拟运行,不实际删除文件
|
||||
|
||||
Returns:
|
||||
dict: 清理结果统计
|
||||
"""
|
||||
try:
|
||||
# 查找所有录音文件并按修改时间排序
|
||||
recording_files = glob.glob("recording_*.wav")
|
||||
|
||||
stats = {
|
||||
'total_files': len(recording_files),
|
||||
'files_to_delete': 0,
|
||||
'files_deleted': 0,
|
||||
'total_size_mb': 0.0,
|
||||
'freed_space_mb': 0.0,
|
||||
'deleted_files': []
|
||||
}
|
||||
|
||||
if not recording_files:
|
||||
print(f"🧹 未找到录音文件")
|
||||
return stats
|
||||
|
||||
# 按修改时间排序(最新的在前)
|
||||
recording_files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
|
||||
|
||||
# 计算总大小
|
||||
for file_path in recording_files:
|
||||
try:
|
||||
file_size = os.path.getsize(file_path)
|
||||
stats['total_size_mb'] += file_size / (1024 * 1024)
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"🧹 开始按数量清理录音文件(保留最新的 {max_files} 个文件)")
|
||||
print(f"📊 找到 {stats['total_files']} 个录音文件,总大小: {stats['total_size_mb']:.2f} MB")
|
||||
|
||||
if dry_run:
|
||||
print(f"🔍 模拟运行模式,不会实际删除文件")
|
||||
|
||||
# 删除超出数量的文件
|
||||
files_to_delete = recording_files[max_files:]
|
||||
stats['files_to_delete'] = len(files_to_delete)
|
||||
|
||||
for file_path in files_to_delete:
|
||||
try:
|
||||
# 获取文件大小
|
||||
file_size = os.path.getsize(file_path)
|
||||
stats['freed_space_mb'] += file_size / (1024 * 1024)
|
||||
|
||||
if not dry_run:
|
||||
# 实际删除文件
|
||||
os.remove(file_path)
|
||||
stats['files_deleted'] += 1
|
||||
stats['deleted_files'].append(os.path.basename(file_path))
|
||||
print(f"🗑️ 已删除: {file_path} ({file_size / 1024:.1f} KB)")
|
||||
else:
|
||||
print(f"🔍 标记删除: {file_path} ({file_size / 1024:.1f} KB)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 删除文件 {file_path} 时出错: {e}")
|
||||
continue
|
||||
|
||||
# 输出清理结果
|
||||
print(f"📊 清理完成:")
|
||||
print(f" - 总文件数: {stats['total_files']}")
|
||||
print(f" - 应删除文件数: {stats['files_to_delete']}")
|
||||
if not dry_run:
|
||||
print(f" - 实际删除文件数: {stats['files_deleted']}")
|
||||
print(f" - 释放空间: {stats['freed_space_mb']:.2f} MB")
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 按数量清理录音文件时出错: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def list_recordings():
|
||||
"""
|
||||
列出所有录音文件及其信息
|
||||
|
||||
Returns:
|
||||
list: 录音文件信息列表
|
||||
"""
|
||||
try:
|
||||
recording_files = glob.glob("recording_*.wav")
|
||||
file_info = []
|
||||
|
||||
for file_path in recording_files:
|
||||
try:
|
||||
stat = os.stat(file_path)
|
||||
file_info.append({
|
||||
'filename': os.path.basename(file_path),
|
||||
'path': file_path,
|
||||
'size_kb': stat.st_size / 1024,
|
||||
'size_mb': stat.st_size / (1024 * 1024),
|
||||
'created_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_ctime)),
|
||||
'modified_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime)),
|
||||
'age_hours': (time.time() - stat.st_mtime) / 3600
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"❌ 获取文件信息 {file_path} 时出错: {e}")
|
||||
continue
|
||||
|
||||
# 按修改时间排序(最新的在前)
|
||||
file_info.sort(key=lambda x: x['modified_time'], reverse=True)
|
||||
|
||||
return file_info
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 列出录音文件时出错: {e}")
|
||||
return []
|
||||
|
||||
def print_recording_summary():
|
||||
"""打印录音文件摘要信息"""
|
||||
recordings = list_recordings()
|
||||
|
||||
if not recordings:
|
||||
print("📁 未找到录音文件")
|
||||
return
|
||||
|
||||
total_size_mb = sum(r['size_mb'] for r in recordings)
|
||||
oldest_file = min(recordings, key=lambda x: x['age_hours'])
|
||||
newest_file = max(recordings, key=lambda x: x['age_hours'])
|
||||
|
||||
print(f"📁 录音文件摘要:")
|
||||
print(f" - 文件数量: {len(recordings)}")
|
||||
print(f" - 总大小: {total_size_mb:.2f} MB")
|
||||
print(f" - 最新文件: {newest_file['filename']} ({newest_file['created_time']})")
|
||||
print(f" - 最旧文件: {oldest_file['filename']} ({oldest_file['created_time']}, {oldest_file['age_hours']:.1f} 小时前)")
|
||||
|
||||
# 显示最近的5个文件
|
||||
print(f"📄 最近的文件:")
|
||||
for i, recording in enumerate(recordings[:5]):
|
||||
print(f" {i+1}. {recording['filename']} - {recording['size_mb']:.2f} MB - {recording['created_time']}")
|
||||
|
||||
if len(recordings) > 5:
|
||||
print(f" ... 还有 {len(recordings) - 5} 个文件")
|
||||
|
||||
|
||||
|
||||
class RecordingState(Enum):
|
||||
@ -164,6 +397,14 @@ class InputProcess:
|
||||
self.tts_app_key = "aGjiRDfUWi"
|
||||
self.tts_speaker = config.get('tts_speaker', "zh_female_wanqudashu_moon_bigtts") if config else "zh_female_wanqudashu_moon_bigtts"
|
||||
|
||||
# 录音文件清理配置
|
||||
self.cleanup_config = config.get('cleanup', {}) if config else {}
|
||||
self.auto_cleanup_enabled = self.cleanup_config.get('auto_cleanup', False)
|
||||
self.retention_hours = self.cleanup_config.get('retention_hours', 24)
|
||||
self.max_files = self.cleanup_config.get('max_files', 50)
|
||||
self.cleanup_interval = self.cleanup_config.get('cleanup_interval', 3600) # 1小时
|
||||
self.last_cleanup_time = time.time()
|
||||
|
||||
# 启动 TTS 工作线程
|
||||
self._start_tts_worker()
|
||||
|
||||
@ -198,7 +439,11 @@ class InputProcess:
|
||||
if should_process_audio:
|
||||
self._process_audio()
|
||||
|
||||
# 3. 短暂休眠,减少CPU占用
|
||||
# 3. 检查是否需要执行自动清理
|
||||
if self.auto_cleanup_enabled:
|
||||
self._check_auto_cleanup()
|
||||
|
||||
# 4. 短暂休眠,减少CPU占用
|
||||
time.sleep(0.01)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
@ -389,6 +634,16 @@ class InputProcess:
|
||||
self._handle_emergency_stop()
|
||||
return
|
||||
|
||||
elif command.command == 'cleanup_recordings':
|
||||
# 清理录音文件命令
|
||||
print("🧹 输入进程收到清理录音文件命令")
|
||||
self._perform_cleanup()
|
||||
|
||||
elif command.command == 'list_recordings':
|
||||
# 列出录音文件命令
|
||||
print("📋 输入进程收到列出录音文件命令")
|
||||
self._list_recordings_info()
|
||||
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
@ -579,6 +834,35 @@ class InputProcess:
|
||||
print(f"❌ 输入进程保存录音失败: {e}")
|
||||
return None
|
||||
|
||||
def _check_auto_cleanup(self):
|
||||
"""检查是否需要执行自动清理"""
|
||||
current_time = time.time()
|
||||
if current_time - self.last_cleanup_time >= self.cleanup_interval:
|
||||
print(f"🧹 执行自动清理录音文件")
|
||||
self._perform_cleanup()
|
||||
self.last_cleanup_time = current_time
|
||||
|
||||
def _perform_cleanup(self):
|
||||
"""执行录音文件清理"""
|
||||
try:
|
||||
if self.retention_hours > 0:
|
||||
print(f"🧹 按时间清理录音文件(保留 {self.retention_hours} 小时内的文件)")
|
||||
cleanup_recordings(retention_hours=self.retention_hours, dry_run=False)
|
||||
|
||||
if self.max_files > 0:
|
||||
print(f"🧹 按数量清理录音文件(保留最新的 {self.max_files} 个文件)")
|
||||
cleanup_recordings_by_count(max_files=self.max_files, dry_run=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 执行清理时出错: {e}")
|
||||
|
||||
def _list_recordings_info(self):
|
||||
"""列出录音文件信息"""
|
||||
try:
|
||||
print_recording_summary()
|
||||
except Exception as e:
|
||||
print(f"❌ 列出录音文件信息时出错: {e}")
|
||||
|
||||
def _cleanup(self):
|
||||
"""清理资源"""
|
||||
# 停止 TTS 工作线程
|
||||
@ -968,9 +1252,13 @@ class OutputProcess:
|
||||
# 播放音频块
|
||||
if chunks_played == 0: # 只在第一次播放时打印详细信息
|
||||
print(f"🔊 开始播放音频块 {chunks_played + 1}")
|
||||
print(f"🔊 添加0.5秒缓冲时间,避免破音...")
|
||||
time.sleep(0.5) # 添加0.5秒缓冲时间
|
||||
|
||||
# 确保播放状态正确
|
||||
if not self.currently_playing:
|
||||
self.currently_playing = False # 先设置为False,确保状态正确变化
|
||||
time.sleep(0.1) # 短暂等待
|
||||
self.currently_playing = True
|
||||
self.last_audio_chunk_time = time.time() # 记录最后播放时间
|
||||
|
||||
@ -1376,6 +1664,10 @@ class OutputProcess:
|
||||
self.is_playing = True
|
||||
self.last_playback_time = 0 # 重置播放时间,避免立即触发冷却期
|
||||
|
||||
# 关键修复:预加载完成时也设置last_audio_chunk_time
|
||||
self.last_audio_chunk_time = time.time()
|
||||
print(f"🎵 预加载完成时设置last_audio_chunk_time = {self.last_audio_chunk_time}")
|
||||
|
||||
# 备用:在缓冲区转移时也设置all_audio_received为True(兜底机制)
|
||||
if not self.all_audio_received:
|
||||
self.all_audio_received = True
|
||||
@ -1398,6 +1690,10 @@ class OutputProcess:
|
||||
self.is_playing = True
|
||||
self.last_playback_time = 0
|
||||
|
||||
# 关键修复:强制转移时也设置last_audio_chunk_time
|
||||
self.last_audio_chunk_time = time.time()
|
||||
print(f"🎵 强制转移时设置last_audio_chunk_time = {self.last_audio_chunk_time}")
|
||||
|
||||
# 备用:强制转移后也设置all_audio_received为True(兜底机制)
|
||||
if not self.all_audio_received:
|
||||
self.all_audio_received = True
|
||||
@ -1444,6 +1740,10 @@ class OutputProcess:
|
||||
self.is_playing = True
|
||||
self.last_playback_time = 0 # 重置播放时间,避免立即触发冷却期
|
||||
|
||||
# 关键修复:最小缓冲区模式下也设置last_audio_chunk_time
|
||||
self.last_audio_chunk_time = time.time()
|
||||
print(f"🎵 最小缓冲区模式下设置last_audio_chunk_time = {self.last_audio_chunk_time}")
|
||||
|
||||
# 备用:最小缓冲区模式下也设置all_audio_received为True(兜底机制)
|
||||
if not self.all_audio_received:
|
||||
self.all_audio_received = True
|
||||
@ -1557,7 +1857,8 @@ class OutputProcess:
|
||||
print("⚠️ 输出进程:未设置事件队列,无法通知主进程播放完成")
|
||||
|
||||
# 额外等待确保音频设备完全停止
|
||||
time.sleep(0.5)
|
||||
print("📡 等待1秒确保音频完全播放完毕...")
|
||||
time.sleep(1.0)
|
||||
print("📡 输出进程:播放完成逻辑执行完毕")
|
||||
|
||||
def _check_enhanced_playback_completion(self):
|
||||
@ -1950,9 +2251,9 @@ class OutputProcess:
|
||||
# 立即设置TTS生成完成状态(因为缓存音频相当于已经生成完成)
|
||||
self.tts_generation_complete = True
|
||||
|
||||
# 对于缓存音频,立即设置all_audio_received为True(关键修复)
|
||||
self.all_audio_received = True
|
||||
print(f"🎵 缓存音频已设置all_audio_received=True")
|
||||
# 重要修复:不在这里立即设置all_audio_received=True
|
||||
# 等待音频实际开始播放时再设置,避免时序问题
|
||||
print(f"🎵 缓存音频已准备好,等待播放开始时设置all_audio_received")
|
||||
|
||||
# 检查是否应该开始播放(关键修复)
|
||||
if (not self.is_playing and len(self.preload_buffer) >= self.preload_size):
|
||||
@ -1962,6 +2263,15 @@ class OutputProcess:
|
||||
self.preload_buffer.clear()
|
||||
self.is_playing = True
|
||||
self.last_playback_time = 0 # 重置播放时间,避免冷却期
|
||||
|
||||
# 关键修复:在音频开始播放时设置last_audio_chunk_time
|
||||
self.last_audio_chunk_time = time.time()
|
||||
print(f"🎵 设置last_audio_chunk_time = {self.last_audio_chunk_time}")
|
||||
|
||||
# 现在可以设置all_audio_received为True
|
||||
self.all_audio_received = True
|
||||
print(f"🎵 缓存音频开始播放,设置all_audio_received=True")
|
||||
|
||||
self.logger.info("开始播放缓存音频(预加载完成)")
|
||||
elif (not self.is_playing and len(self.preload_buffer) > 0):
|
||||
print(f"🎵 缓存音频已添加但未达到预加载大小,强制开始播放...")
|
||||
@ -1970,6 +2280,15 @@ class OutputProcess:
|
||||
self.preload_buffer.clear()
|
||||
self.is_playing = True
|
||||
self.last_playback_time = 0 # 重置播放时间,避免冷却期
|
||||
|
||||
# 关键修复:在音频开始播放时设置last_audio_chunk_time
|
||||
self.last_audio_chunk_time = time.time()
|
||||
print(f"🎵 设置last_audio_chunk_time = {self.last_audio_chunk_time}")
|
||||
|
||||
# 现在可以设置all_audio_received为True
|
||||
self.all_audio_received = True
|
||||
print(f"🎵 缓存音频强制开始播放,设置all_audio_received=True")
|
||||
|
||||
self.logger.info("强制开始播放缓存音频")
|
||||
|
||||
# 发送TTS完成信号到主队列
|
||||
@ -1986,7 +2305,9 @@ class OutputProcess:
|
||||
self.logger.error(f"缓存音频处理失败: {e}")
|
||||
# 即使失败也要设置TTS完成状态,避免系统卡住
|
||||
self.tts_generation_complete = True
|
||||
print(f"🔧 缓存音频处理失败,设置TTS完成状态为True")
|
||||
# 失败时也要设置all_audio_received避免死锁
|
||||
self.all_audio_received = True
|
||||
print(f"🔧 缓存音频处理失败,设置完成状态为True")
|
||||
|
||||
def _generate_tts_audio(self, text, character_name=None):
|
||||
"""生成TTS音频数据并发送到统一播放队列 - 借鉴 recorder.py 的流式处理"""
|
||||
|
||||
71
cleanup_recordings.py
Normal file
71
cleanup_recordings.py
Normal file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
录音文件清理工具
|
||||
用于清理过期的录音文件,管理磁盘空间
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
from audio_processes import (
|
||||
cleanup_recordings,
|
||||
cleanup_recordings_by_count,
|
||||
list_recordings,
|
||||
print_recording_summary
|
||||
)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='录音文件清理工具')
|
||||
parser.add_argument('--list', '-l', action='store_true', help='列出所有录音文件')
|
||||
parser.add_argument('--summary', '-s', action='store_true', help='显示录音文件摘要')
|
||||
parser.add_argument('--cleanup-time', '-t', type=int, default=24,
|
||||
help='按时间清理,保留最近N小时的文件(默认24小时)')
|
||||
parser.add_argument('--cleanup-count', '-c', type=int, default=50,
|
||||
help='按数量清理,保留最新的N个文件(默认50个)')
|
||||
parser.add_argument('--dry-run', '-d', action='store_true',
|
||||
help='模拟运行,不实际删除文件')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list:
|
||||
print("📋 录音文件列表:")
|
||||
recordings = list_recordings()
|
||||
if not recordings:
|
||||
print(" 未找到录音文件")
|
||||
else:
|
||||
for i, recording in enumerate(recordings, 1):
|
||||
print(f" {i}. {recording['filename']}")
|
||||
print(f" 大小: {recording['size_mb']:.2f} MB")
|
||||
print(f" 创建时间: {recording['created_time']}")
|
||||
print(f" 修改时间: {recording['modified_time']}")
|
||||
print(f" 文件年龄: {recording['age_hours']:.1f} 小时")
|
||||
print()
|
||||
return
|
||||
|
||||
if args.summary:
|
||||
print_recording_summary()
|
||||
return
|
||||
|
||||
if args.cleanup_time > 0:
|
||||
print(f"🧹 按时间清理录音文件(保留 {args.cleanup_time} 小时内的文件)")
|
||||
cleanup_recordings(retention_hours=args.cleanup_time, dry_run=args.dry_run)
|
||||
|
||||
if args.cleanup_count > 0:
|
||||
print(f"🧹 按数量清理录音文件(保留最新的 {args.cleanup_count} 个文件)")
|
||||
cleanup_recordings_by_count(max_files=args.cleanup_count, dry_run=args.dry_run)
|
||||
|
||||
if not any([args.list, args.summary, args.cleanup_time > 0, args.cleanup_count > 0]):
|
||||
print("📖 录音文件清理工具")
|
||||
print("使用 --help 查看帮助信息")
|
||||
print()
|
||||
print("常用命令:")
|
||||
print(" python cleanup_recordings.py --summary # 显示摘要")
|
||||
print(" python cleanup_recordings.py --list # 列出所有文件")
|
||||
print(" python cleanup_recordings.py --cleanup-time 24 # 保留24小时内的文件")
|
||||
print(" python cleanup_recordings.py --cleanup-count 30 # 保留最新的30个文件")
|
||||
print(" python cleanup_recordings.py -t 24 -c 30 -d # 模拟运行")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,3 +0,0 @@
|
||||
2025-09-21 18:27:29 - InputProcess_logger - INFO - 日志系统初始化完成 - 进程: InputProcess
|
||||
2025-09-21 18:27:29 - InputProcess_logger - INFO - 日志文件: logs/InputProcess_20250921_182729.log
|
||||
2025-09-21 18:27:29 - InputProcess_logger - INFO - [InputProcess] TTS工作线程已启动
|
||||
@ -1,3 +0,0 @@
|
||||
2025-09-21 18:32:04 - InputProcess_logger - INFO - 日志系统初始化完成 - 进程: InputProcess
|
||||
2025-09-21 18:32:04 - InputProcess_logger - INFO - 日志文件: logs/InputProcess_20250921_183204.log
|
||||
2025-09-21 18:32:04 - InputProcess_logger - INFO - [InputProcess] TTS工作线程已启动
|
||||
@ -1,3 +0,0 @@
|
||||
2025-09-21 20:08:51 - InputProcess_logger - INFO - 日志系统初始化完成 - 进程: InputProcess
|
||||
2025-09-21 20:08:51 - InputProcess_logger - INFO - 日志文件: logs/InputProcess_20250921_200851.log
|
||||
2025-09-21 20:08:51 - InputProcess_logger - INFO - [InputProcess] TTS工作线程已启动
|
||||
@ -1,4 +0,0 @@
|
||||
2025-09-21 20:09:16 - InputProcess_logger - INFO - 日志系统初始化完成 - 进程: InputProcess
|
||||
2025-09-21 20:09:16 - InputProcess_logger - INFO - 日志文件: logs/InputProcess_20250921_200916.log
|
||||
2025-09-21 20:09:16 - InputProcess_logger - INFO - [InputProcess] TTS工作线程已启动
|
||||
2025-09-21 20:09:16 - InputProcess_logger - INFO - [InputProcess] 输入进程启动
|
||||
@ -1,4 +0,0 @@
|
||||
2025-09-25 09:58:09 - InputProcess_logger - INFO - 日志系统初始化完成 - 进程: InputProcess
|
||||
2025-09-25 09:58:09 - InputProcess_logger - INFO - 日志文件: logs/InputProcess_20250925_095809.log
|
||||
2025-09-25 09:58:09 - InputProcess_logger - INFO - [InputProcess] TTS工作线程已启动
|
||||
2025-09-25 09:58:09 - InputProcess_logger - INFO - [InputProcess] 输入进程启动
|
||||
@ -1,9 +0,0 @@
|
||||
2025-09-21 20:09:16 - OutputProcess_logger - INFO - 日志系统初始化完成 - 进程: OutputProcess
|
||||
2025-09-21 20:09:16 - OutputProcess_logger - INFO - 日志文件: logs/OutputProcess_20250921_200916.log
|
||||
2025-09-21 20:09:16 - OutputProcess_logger - INFO - [OutputProcess] 播放工作线程已启动
|
||||
2025-09-21 20:09:16 - OutputProcess_logger - INFO - [OutputProcess] TTS工作线程已启动
|
||||
2025-09-21 20:09:16 - OutputProcess_logger - INFO - [OutputProcess] 输出进程启动
|
||||
2025-09-21 20:09:17 - OutputProcess_logger - INFO - [OutputProcess] 音频设备初始化成功
|
||||
2025-09-21 20:09:54 - OutputProcess_logger - ERROR - [OutputProcess] 处理音频队列时出错:
|
||||
2025-09-21 20:09:54 - OutputProcess_logger - ERROR - [OutputProcess] 输出进程错误:
|
||||
2025-09-21 20:09:57 - OutputProcess_logger - INFO - [OutputProcess] 输出进程退出
|
||||
@ -1,4 +0,0 @@
|
||||
2025-09-23 13:18:24 - OutputProcess_logger - INFO - 日志系统初始化完成 - 进程: OutputProcess
|
||||
2025-09-23 13:18:24 - OutputProcess_logger - INFO - 日志文件: logs/OutputProcess_20250923_131824.log
|
||||
2025-09-23 13:18:24 - OutputProcess_logger - INFO - [OutputProcess] 播放工作线程已启动
|
||||
2025-09-23 13:18:24 - OutputProcess_logger - INFO - [OutputProcess] TTS工作线程已启动
|
||||
@ -1,7 +0,0 @@
|
||||
2025-09-25 09:58:09 - OutputProcess_logger - INFO - 日志系统初始化完成 - 进程: OutputProcess
|
||||
2025-09-25 09:58:09 - OutputProcess_logger - INFO - 日志文件: logs/OutputProcess_20250925_095809.log
|
||||
2025-09-25 09:58:09 - OutputProcess_logger - INFO - [OutputProcess] 播放工作线程已启动
|
||||
2025-09-25 09:58:09 - OutputProcess_logger - INFO - [OutputProcess] TTS工作线程已启动
|
||||
2025-09-25 09:58:09 - OutputProcess_logger - INFO - [OutputProcess] 输出进程启动
|
||||
2025-09-25 09:58:10 - OutputProcess_logger - INFO - [OutputProcess] 音频设备初始化成功
|
||||
2025-09-25 09:58:32 - OutputProcess_logger - INFO - [OutputProcess] 输出进程收到中断信号
|
||||
91
test_cached_timing_fix.py
Normal file
91
test_cached_timing_fix.py
Normal file
@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
测试缓存音频时序修复
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
|
||||
from audio_processes import OutputProcess
|
||||
import multiprocessing as mp
|
||||
|
||||
def test_cached_audio_timing():
|
||||
"""测试缓存音频时序修复"""
|
||||
print("🧪 开始测试缓存音频时序修复...")
|
||||
|
||||
# 创建测试队列
|
||||
audio_queue = mp.Queue(maxsize=100)
|
||||
event_queue = mp.Queue(maxsize=100)
|
||||
|
||||
# 创建输出进程实例
|
||||
config = {
|
||||
'buffer_size': 1000,
|
||||
'show_progress': True,
|
||||
'progress_interval': 100,
|
||||
'tts_speaker': 'zh_female_wanqudashu_moon_bigtts'
|
||||
}
|
||||
|
||||
output_process = OutputProcess(audio_queue, config, event_queue)
|
||||
|
||||
# 测试1: 检查关键时序变量
|
||||
print("\n📋 测试1: 检查关键时序变量")
|
||||
assert hasattr(output_process, 'last_audio_chunk_time'), "缺少 last_audio_chunk_time 变量"
|
||||
assert hasattr(output_process, 'all_audio_received'), "缺少 all_audio_received 变量"
|
||||
print(f"✅ 初始 last_audio_chunk_time: {output_process.last_audio_chunk_time}")
|
||||
print(f"✅ 初始 all_audio_received: {output_process.all_audio_received}")
|
||||
|
||||
# 测试2: 模拟缓存音频处理前的状态
|
||||
print("\n📋 测试2: 检查初始状态")
|
||||
initial_time = output_process.last_audio_chunk_time
|
||||
print(f"✅ 初始时间戳: {initial_time}")
|
||||
|
||||
# 测试3: 验证我们的修复逻辑
|
||||
print("\n📋 测试3: 验证修复逻辑")
|
||||
|
||||
# 模拟设置时序变量(这是我们的修复核心)
|
||||
test_time = time.time()
|
||||
output_process.last_audio_chunk_time = test_time
|
||||
output_process.all_audio_received = True
|
||||
|
||||
print(f"✅ 设置后 last_audio_chunk_time: {output_process.last_audio_chunk_time}")
|
||||
print(f"✅ 设置后 all_audio_received: {output_process.all_audio_received}")
|
||||
|
||||
# 验证时间差计算
|
||||
time_diff = time.time() - output_process.last_audio_chunk_time
|
||||
print(f"✅ 时间差计算: {time_diff:.3f}秒(应该接近0)")
|
||||
|
||||
# 测试4: 检查是否会有异常的时间差(修复前的问题)
|
||||
print("\n📋 测试4: 检查异常时间差问题")
|
||||
|
||||
# 模拟修复前的问题:last_audio_chunk_time为0或很老的时间
|
||||
old_time = output_process.last_audio_chunk_time
|
||||
output_process.last_audio_chunk_time = 0 # 模拟未初始化的情况
|
||||
|
||||
# 模拟播放完成检测逻辑
|
||||
if output_process.last_audio_chunk_time > 0:
|
||||
time_since_last = time.time() - output_process.last_audio_chunk_time
|
||||
print(f"⚠️ 时间差: {time_since_last:.3f}秒")
|
||||
else:
|
||||
print("⚠️ last_audio_chunk_time未设置,这会导致立即完成")
|
||||
|
||||
# 恢复正确的时间
|
||||
output_process.last_audio_chunk_time = old_time
|
||||
|
||||
print("\n🎉 时序修复验证完成!")
|
||||
print("📝 修复要点:")
|
||||
print(" 1. 在缓存音频开始播放时设置last_audio_chunk_time")
|
||||
print(" 2. 确保all_audio_received在适当时机设置")
|
||||
print(" 3. 避免出现49.292秒的异常时间差")
|
||||
|
||||
# 清理
|
||||
output_process.running = False
|
||||
time.sleep(0.1) # 给线程时间清理
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cached_audio_timing()
|
||||
Loading…
Reference in New Issue
Block a user