开发AB听力测试APP:多音频同步播放与切换时时间戳保留问题
AB听力测试音频切换:精确获取帧编号并续播方案
针对你的需求,推荐直接基于音频播放流的帧跟踪来实现,比计时法精度高得多,以下是两种可行的实现思路:
方法1:PyAudio + SoundFile 样本级精确控制
SoundFile负责读取音频帧数据,PyAudio处理播放流,同时实时维护当前播放的帧编号,完全避免计时误差。
核心逻辑
- 加载两个音频文件,确保采样率、通道数一致(AB测试文件通常满足此条件)
- 用PyAudio创建播放流,通过回调函数实时更新已播放的帧编号
- 切换音频时,暂停当前流,从记录的帧位置读取另一音频的对应片段继续播放
示例代码片段:
import soundfile as sf import pyaudio # 加载音频文件 audio_a, samplerate = sf.read("audio_a.wav") audio_b, _ = sf.read("audio_b.wav") channels = audio_a.shape[1] if len(audio_a.shape) > 1 else 1 audio_format = pyaudio.paFloat32 # 根据实际音频格式调整 # 全局变量跟踪播放状态 p = pyaudio.PyAudio() current_frame = 0 current_audio = audio_a stream = None def start_playback(): global stream, current_frame if stream: stream.stop_stream() stream.close() # 从当前帧位置读取音频数据 play_data = current_audio[current_frame:] stream = p.open(format=audio_format, channels=channels, rate=samplerate, output=True, stream_callback=play_callback) stream.start_stream() def play_callback(in_data, frame_count, time_info, status): global current_frame end_frame = current_frame + frame_count # 处理播放到末尾的情况 if end_frame > len(current_audio): play_data = current_audio[current_frame:] current_frame = len(current_audio) return (play_data, pyaudio.paComplete) else: play_data = current_audio[current_frame:end_frame] current_frame = end_frame return (play_data, pyaudio.paContinue) def switch_audio(): global current_audio, current_frame stream.stop_stream() # 切换音频源 current_audio = audio_b if current_audio is audio_a else audio_a start_playback()
方法2:python-sounddevice 简化流控制
python-sounddevice是PortAudio的轻量封装,API更简洁,同样支持精确帧定位:
核心逻辑
- 加载音频文件后,用
sounddevice.play()启动播放,可指定起始帧 - 通过流的
time属性获取当前播放时间,转换成帧编号(帧编号=播放时间×采样率) - 切换时记录当前帧,停止当前播放后,从该帧启动另一音频
示例代码片段:
import sounddevice as sd import soundfile as sf audio_a, samplerate = sf.read("audio_a.wav") audio_b, _ = sf.read("audio_b.wav") current_frame = 0 current_audio = audio_a def play_from_saved_frame(): sd.stop() sd.play(current_audio[current_frame:], samplerate) def switch_audio(): global current_audio, current_frame # 获取当前播放时间并转换为帧编号 current_time = sd.get_stream().time current_frame = int(current_time * samplerate) # 切换音频源 current_audio = audio_b if current_audio is audio_a else audio_a play_from_saved_frame()
为什么不推荐datetime计时法?
- 系统计时受进程调度、系统负载影响,存在不可控的精度误差,会导致帧定位偏移
- 直接跟踪播放的帧数据是样本级精确的,完全匹配音频实际播放位置,适合AB听力测试这种对同步性要求高的场景
内容的提问来源于stack exchange,提问作者DSP novice
相关产品推荐
相关产品推荐

