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

如何基于WebRTC(aiortc)服务器与浏览器客户端实现流畅音频播放

问题修复方案

一、单声道转立体声代码优化

你当前的单转立体声逻辑在结构上可行,但忽略了数据类型匹配和WebRTC AudioFrame的格式要求,这是导致音频失真、卡顿的潜在原因。修改后的代码如下:

@staticmethod
def resample_to_stereo(mono_audio_frame: np.ndarray) -> np.ndarray:
    """
    将单声道24000Hz音频帧转换为立体声帧
    Args:
        mono_audio_frame: 输入单声道数据,1D NumPy数组
    Returns:
        交错格式的立体声1D NumPy数组
    """
    # 强制匹配WebRTC AudioFrame常用的int16格式(PCM 16位)
    if mono_audio_frame.dtype != np.int16:
        mono_audio_frame = mono_audio_frame.astype(np.int16)
    
    # 用tile生成双声道,避免二维数组转置的冗余操作
    stereo_audio_frame = np.tile(mono_audio_frame, 2).reshape(-1, 2).ravel()
    
    return stereo_audio_frame

关键修改点:

  • 统一数据类型:WebRTC音频帧默认使用int16格式,若输入为float32或其他类型,必须转换,否则会出现杂音或播放异常。
  • 简化数组操作:用np.tile替代二维数组转置,效率更高且避免形状转换时的潜在错误。

二、AudioFifo读取与发送逻辑修复

卡顿和回声的核心问题大概率出在采样率不匹配和帧时序控制上,以下是修复后的代码:

# 读取Fifo帧并发送到浏览器,阻塞直到有数据
frame = None
while self.is_active():
    frame = self.audio_fifo.read(samples=self.frames_per_buffer)
    if frame is not None:
        if not self.is_playing_back:
            print("开始播放音频")
            self.is_playing_back = True
        
        # 关键:WebRTC音频轨道必须使用固定采样率(通常为48000Hz)
        # 若TTS输出是24000Hz,需先将音频重采样到48000Hz,再存入Fifo,而非直接修改sample_rate属性
        frame.sample_rate = CONST.WEB_RTC_AUDIO_SAMPLE_RATE
        
        if DEBUG_FILES:
            # 立体声帧to_ndarray()返回(2, N)数组,需转为交错一维数组再写入文件
            audio_array = frame.to_ndarray().ravel()
            audio_bytes = audio_array.tobytes()
            self.sendframe_48.write(audio_bytes)
        
        ftime = frame.time if (frame.time is not None and frame.time > 0) else 0
        self.logger.info(
            f"发送帧 pts={frame.pts} duration={frame.duration} time={ftime} size={frame.samples} rate={frame.rate} sample_rate={frame.sample_rate}"
        )
        break

if frame is not None:
    # 根据实际帧时长计算sleep时间,保证时序对齐
    frame_duration = frame.samples / frame.sample_rate
    await asyncio.sleep(frame_duration)
    return frame
else:
    # 无数据时发送静音帧,sleep时间匹配WebRTC标准帧间隔(20ms)
    await asyncio.sleep(0.02)
    frame = self.get_silence_frame()
    frame.sample_rate = CONST.WEB_RTC_AUDIO_SAMPLE_RATE
    ftime = frame.time if (frame.time is not None and frame.time > 0) else 0
    self.logger.info(
        f"发送静音帧 pts={frame.pts} duration={frame.duration} time={ftime} size={frame.samples} rate={frame.rate} sample_rate={frame.sample_rate}"
    )
    return frame

关键修复点:

  1. 采样率统一:WebRTC音频轨道必须使用固定采样率(几乎都是48000Hz),不能直接将TTS的24000Hz赋值给frame.sample_rate——这会导致浏览器按48000Hz解析24000Hz数据,播放速度加倍,进而出现卡顿、音调异常。需先将24000Hz单声道音频重采样到48000Hz,再转立体声存入Fifo。
  2. 帧时序修正:
    • 之前的sleep时间混用不同采样率,导致帧发送间隔错误,现在统一用当前帧的实际采样率计算时长。
    • 静音帧sleep时间固定为20ms(WebRTC标准帧间隔),避免时序混乱。
  3. 立体声数据读取修正:frame.to_ndarray()返回的立体声数组是(2, N)结构,需用ravel()转为交错一维数组再写入调试文件,否则会导致调试音频格式错误。

三、额外排查建议

  • 回声问题:若修复后仍有回声,检查浏览器端是否开启麦克风监听,或WebRTC音频轨道是否同时开启输入输出(可能导致本地音频循环)。
  • Fifo容量设置:确保AudioFifo容量足够,建议设置为5-10个标准帧大小(每个20ms/48000Hz的帧包含960个采样点),避免数据溢出或欠载。
  • 专业采样率转换:推荐用librosa进行采样率转换,避免手动插值导致的失真:
    import librosa
    # 将24000Hz单声道音频转48000Hz
    resampled_mono = librosa.resample(mono_audio_frame, orig_sr=24000, target_sr=48000)
    

内容的提问来源于stack exchange,提问作者wnm3

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 08:54:51