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

网页麦克风音频经WebSocket传FastAPI后端播放故障排查

实时音频流系统杂音与断续问题解决

核心问题总结

你搭建的实时音频流系统中,前端通过MediaRecorder以audio/webm格式、250ms间隔发送44100采样率的音频到FastAPI后端,后端用PyAudio播放时仅能听到断续杂音,无法实现连续播放。核心原因有两点:

  1. PyAudio不支持直接解码audio/webm封装格式——webm是容器格式,内部包含Opus编码的音频数据,而PyAudio仅能播放原始PCM音频;
  2. 未处理音频流的时序缓冲,250ms间隔发送的数据包如果没有正确拼接、缓冲,会导致播放断续。

前端代码调整方案

放弃MediaRecorder的webm封装,直接获取原始PCM音频数据发送,避免后端解码成本:

navigator.mediaDevices.getUserMedia({ audio: {sampleRate: 44100, channelCount: 1} }).then((stream) => {
    sockets[speaker_name][stream_name] = stream;
    if (turnOn) {
        const audioContext = new AudioContext({ sampleRate: 44100 });
        const source = audioContext.createMediaStreamSource(stream);
        const processor = audioContext.createScriptProcessor(4096, 1, 1);
        
        const socket = new WebSocket('ws://127.0.0.1:8000/micstream/');
        sockets[speaker_name][sname] = socket;
        sockets[speaker_name][mic] = processor;

        socket.onopen = () => {
            console.log(stream.getTracks()[0].getConstraints());
            console.log({ event: 'onopen' });
            
            processor.onaudioprocess = (event) => {
                const inputBuffer = event.inputBuffer;
                const channelData = inputBuffer.getChannelData(0);
                // 将Float32Array转为16位整数PCM(适配后端播放库)
                const pcmData = new Int16Array(channelData.length);
                for (let i = 0; i < channelData.length; i++) {
                    const sample = Math.max(-1, Math.min(1, channelData[i]));
                    pcmData[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
                }
                if (socket.readyState === 1) {
                    socket.send(pcmData.buffer);
                }
            };

            source.connect(processor);
            processor.connect(audioContext.destination);
        };
    }
});

后端替代方案(支持选择扬声器)

替换PyAudio为sounddevice库,它支持直接指定输出设备,且原生处理PCM数据更便捷。提供两种方案:

方案1:直接播放前端发送的原始PCM

import sounddevice as sd
import numpy as np
from fastapi import WebSocket

# 查看可用输出设备列表,获取目标扬声器的索引
print(sd.query_devices())

# 配置音频参数
RATE = 44100
CHANNELS = 1
DTYPE = 'int16'
# 替换为你的扬声器设备索引
OUTPUT_DEVICE_INDEX = 2

async def micstream_endpoint(websocket: WebSocket):
    await websocket.accept()
    # 打开指定扬声器的输出流
    with sd.OutputStream(samplerate=RATE, channels=CHANNELS, dtype=DTYPE, device=OUTPUT_DEVICE_INDEX) as stream:
        while True:
            data = await websocket.receive_bytes()
            # 将字节数据转为numpy数组
            pcm_data = np.frombuffer(data, dtype=DTYPE)
            # 写入音频流播放
            stream.write(pcm_data)

方案2:解码audio/webm后播放(若保留前端MediaRecorder)

需用pyav库解码webm容器中的音频数据:

import sounddevice as sd
import av
import io
import numpy as np
from fastapi import WebSocket

RATE = 44100
CHANNELS = 1
DTYPE = 'int16'
OUTPUT_DEVICE_INDEX = 2

async def micstream_endpoint(websocket: WebSocket):
    await websocket.accept()
    with sd.OutputStream(samplerate=RATE, channels=CHANNELS, dtype=DTYPE, device=OUTPUT_DEVICE_INDEX) as stream:
        while True:
            webm_data = await websocket.receive_bytes()
            # 解码webm为PCM
            container = av.open(io.BytesIO(webm_data))
            for frame in container.decode(audio=0):
                pcm_data = frame.to_ndarray().flatten()
                # 转换为int16格式适配播放
                pcm_data = (pcm_data * 32767).astype(DTYPE)
                stream.write(pcm_data)

关键注意事项

  • 前端必须保证音频参数(采样率、声道数、位深)与后端完全一致;
  • 后端播放时建议维持一个缓冲队列,抵消网络抖动带来的断续问题;
  • sounddevice的设备索引可通过sd.query_devices()命令查看,选择对应扬声器的索引即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 08:51:06