网页麦克风音频经WebSocket传FastAPI后端播放故障排查
实时音频流系统杂音与断续问题解决
核心问题总结
你搭建的实时音频流系统中,前端通过MediaRecorder以audio/webm格式、250ms间隔发送44100采样率的音频到FastAPI后端,后端用PyAudio播放时仅能听到断续杂音,无法实现连续播放。核心原因有两点:
- PyAudio不支持直接解码
audio/webm封装格式——webm是容器格式,内部包含Opus编码的音频数据,而PyAudio仅能播放原始PCM音频; - 未处理音频流的时序缓冲,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
相关产品推荐
相关产品推荐

