You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开发AB听力测试APP:多音频同步播放与切换时时间戳保留问题

AB听力测试音频切换:精确获取帧编号并续播方案

针对你的需求,推荐直接基于音频播放流的帧跟踪来实现,比计时法精度高得多,以下是两种可行的实现思路:

方法1:PyAudio + SoundFile 样本级精确控制

SoundFile负责读取音频帧数据,PyAudio处理播放流,同时实时维护当前播放的帧编号,完全避免计时误差。

核心逻辑

  1. 加载两个音频文件,确保采样率、通道数一致(AB测试文件通常满足此条件)
  2. 用PyAudio创建播放流,通过回调函数实时更新已播放的帧编号
  3. 切换音频时,暂停当前流,从记录的帧位置读取另一音频的对应片段继续播放

示例代码片段:

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更简洁,同样支持精确帧定位:

核心逻辑

  1. 加载音频文件后,用sounddevice.play()启动播放,可指定起始帧
  2. 通过流的time属性获取当前播放时间,转换成帧编号(帧编号=播放时间×采样率)
  3. 切换时记录当前帧,停止当前播放后,从该帧启动另一音频

示例代码片段:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 18:15:38