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

PyAudio音频流经WebSocket传输至网页JS播放的报错解决咨询

解决WebSocket传输PCM音频流的网页播放问题

问题根源

decodeAudioData 是用于解码**完整的编码音频文件(如MP3、WAV)**的API,无法处理WebSocket传输的原始PCM音频流,这就是你报错的核心原因。要播放实时音频流,得用Web Audio API直接处理裸PCM数据。

解决方案步骤

1. 确保Python端输出标准PCM格式

先统一前后端的音频参数,确保pyaudiowpatch输出的是标准16位PCM数据,示例代码:

import pyaudiowpatch as pyaudio
import asyncio
import websockets

# 前后端必须一致的音频参数
FORMAT = pyaudio.paInt16  # 16位整数PCM
CHANNELS = 2  # 立体声,可改为1单声道
RATE = 44100  # 采样率
CHUNK = 1024  # 每次传输的帧数量

async def audio_server(websocket):
    with pyaudio.PyAudio() as p:
        # 获取系统内录设备的音频流
        with p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    input_device_index=p.get_default_loopback_device_info()["index"],
                    frames_per_buffer=CHUNK) as stream:
            while True:
                data = stream.read(CHUNK)
                await websocket.send(data)

async def main():
    async with websockets.serve(audio_server, "localhost", 8765):
        await asyncio.Future()  # 保持服务运行

if __name__ == "__main__":
    asyncio.run(main())

关键:必须使用paInt16格式,且采样率、声道数、帧大小参数要和前端完全匹配。

2. 前端JS用Web Audio API处理实时PCM流

用AudioContext配合ScriptProcessorNode(兼容性好)或AudioWorklet(现代高性能方案)直接处理PCM数据,以下是两种实现方式:

方案一:ScriptProcessorNode(兼容多数浏览器)

const audioContext = new (window.AudioContext || window.webkitAudioContext)({
    sampleRate: 44100  // 和Python端RATE一致
});
// 创建音频处理节点,参数:缓冲区大小、输入声道数、输出声道数
const scriptNode = audioContext.createScriptProcessor(1024, 2, 2);

scriptNode.onaudioprocess = function(event) {
    const outputBuffer = event.outputBuffer;
    if (audioBufferQueue.length === 0) {
        // 无数据时输出静音
        for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
            outputBuffer.getChannelData(channel).fill(0);
        }
        return;
    }

    const inputData = audioBufferQueue.shift();
    for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
        const outputData = outputBuffer.getChannelData(channel);
        // 将16位PCM转换为Web Audio需要的-1~1浮点值
        for (let i = 0; i < outputData.length; i++) {
            const offset = (i * outputBuffer.numberOfChannels + channel) * 2;
            if (offset + 1 < inputData.length) {
                // 合并两个字节为16位整数
                const sample = inputData.getInt16(offset, true);
                outputData[i] = sample / 32768;
            } else {
                outputData[i] = 0;
            }
        }
    }
};

scriptNode.connect(audioContext.destination);

// 缓存音频数据的队列
const audioBufferQueue = [];

// 建立WebSocket连接
const ws = new WebSocket('ws://localhost:8765');
ws.binaryType = 'arraybuffer';  // 必须设置为arraybuffer接收二进制数据

ws.onmessage = function(event) {
    audioBufferQueue.push(new DataView(event.data));
};

// 浏览器要求用户交互才能激活音频上下文
document.addEventListener('click', () => {
    if (audioContext.state === 'suspended') {
        audioContext.resume();
    }
});

方案二:AudioWorklet(现代高性能方案)

  1. 新建audio-worklet-processor.js文件:
class AudioStreamProcessor extends AudioWorkletProcessor {
    constructor() {
        super();
        this.bufferQueue = [];
        this.port.onmessage = (event) => {
            this.bufferQueue.push(new DataView(event.data));
        };
    }

    process(inputs, outputs) {
        const output = outputs[0];
        const channelCount = output.length;
        const frameCount = output[0].length;

        if (this.bufferQueue.length === 0) {
            for (let channel = 0; channel < channelCount; channel++) {
                output[channel].fill(0);
            }
            return true;
        }

        const inputBuffer = this.bufferQueue[0];
        let bytesUsed = 0;

        for (let channel = 0; channel < channelCount; channel++) {
            const channelData = output[channel];
            for (let i = 0; i < frameCount; i++) {
                const offset = (i * channelCount + channel) * 2;
                if (offset + 1 < inputBuffer.byteLength) {
                    const sample = inputBuffer.getInt16(offset, true);
                    channelData[i] = sample / 32768;
                    bytesUsed += 2;
                } else {
                    channelData[i] = 0;
                }
            }
        }

        // 处理剩余未用完的缓冲区数据
        if (bytesUsed >= inputBuffer.byteLength) {
            this.bufferQueue.shift();
        } else {
            this.bufferQueue[0] = new DataView(inputBuffer.buffer, bytesUsed);
        }

        return true;
    }
}

registerProcessor('audio-stream-processor', AudioStreamProcessor);
  1. 前端主JS代码:
async function initAudioStream() {
    const audioContext = new (window.AudioContext || window.webkitAudioContext)({
        sampleRate: 44100
    });

    // 加载AudioWorklet处理器
    await audioContext.audioWorklet.addModule('audio-worklet-processor.js');
    const workletNode = new AudioWorkletNode(audioContext, 'audio-stream-processor');
    workletNode.connect(audioContext.destination);

    const ws = new WebSocket('ws://localhost:8765');
    ws.binaryType = 'arraybuffer';

    ws.onmessage = (event) => {
        workletNode.port.postMessage(event.data);
    };

    // 激活音频上下文
    document.addEventListener('click', () => {
        if (audioContext.state === 'suspended') {
            audioContext.resume();
        }
    });
}

initAudioStream();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 22:52:48