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

Node.js实时转码WebSocket传输的Opus音频至PCM报错,求可行方案

实时转码Chrome录制的WebM/Opus音频为PCM的解决方案

需求

将Chrome端通过MediaRecorder录制的audio/webm; codecs=opus音频,经WebSocket传输至服务器后,实时转码为PCM格式转发至其他服务。

客户端实现代码

let recorder: MediaRecorder;
socket.on('connect', () => {
  navigator.mediaDevices
    .getUserMedia({
      audio: {
        sampleRate: 48000,
        channelCount: 1,
      },
      video: false,
    })
    .then(async (stream) => {
      const options = {
        audioBitsPerSecond: 256000,
        mimeType: 'audio/webm; codecs=opus',
      };
      recorder = new MediaRecorder(stream, options);
      recorder.ondataavailable = async (e) => {
        if (e.data.size <= 0) {
          return false;
        }
        const buffArray = await e.data.arrayBuffer();
        socket.emit('audioStream', new Uint8Array(buffArray));
      };
      recorder.start(20); // 每20ms生成一个分片
    })
})

此前尝试的问题

1. 使用audify/node-opus直接解码

代码:

ws.on('audioStream', message => {
  const buffer = Buffer.isBuffer(message) ? message : Buffer.from(message);
  const decoder = new OpusDecoder(48000, 1);
  const frameSize = 960; // 20ms对应帧大小
  const decoded = decoder.decode(buffer, frameSize);
  // 后续PCM处理逻辑
})

报错:TypeError: The compressed data passed is corrupted
原因:MediaRecorder输出的是WebM容器包裹的Opus数据,而非裸Opus帧,直接用Opus解码器无法解析容器结构。

2. 使用prism-media但每次新建解复用器

代码:

ws.on('audioStream', message => {
  const buffer = Buffer.isBuffer(message) ? message : Buffer.from(message);
  const demuxer = new prism.opus.WebmDemuxer();
  const decodingStream = new prism.opus.Decoder({
      rate: 48000,
      channels: 1,
      frameSize: 960,
  });
  const decoded = new Promise((resolve, reject) => {
      demuxer.pipe(decodingStream).on('data', (chunk) => resolve(chunk));
      demuxer.write(buffer);
    });
})

报错:Did not find the EBML tag at the start of the stream
原因:WebM的EBML文件头仅存在于第一个分片,后续分片无头部信息。每次新建解复用器会丢失上下文,无法识别后续分片。

可行转码方案

方案一:FFmpeg流式处理(推荐,跨语言通用)

利用FFmpeg成熟的容器解复用与解码能力,通过标准输入输出实现流式处理。以Node.js为例:

const { spawn } = require('child_process');

// 启动FFmpeg进程,配置输入输出参数
const ffmpegProcess = spawn('ffmpeg', [
  '-i', 'pipe:0',          // 从标准输入读取WebM流
  '-f', 's16le',           // 输出16位小端PCM格式
  '-ar', '48000',          // 匹配客户端采样率
  '-ac', '1',              // 单声道
  '-vn',                   // 忽略视频(无视频时可省略)
  '-loglevel', 'error',    // 仅输出错误日志
  'pipe:1'                 // 输出PCM到标准输出
]);

// 监听FFmpeg输出的PCM数据,转发至其他服务
ffmpegProcess.stdout.on('data', (pcmChunk) => {
  // 此处添加发送PCM至目标服务的逻辑
});

// 处理WebSocket接收的音频分片,写入FFmpeg标准输入
ws.on('audioStream', (message) => {
  const buffer = Buffer.isBuffer(message) ? message : Buffer.from(message);
  ffmpegProcess.stdin.write(buffer);
});

// 处理FFmpeg错误日志
ffmpegProcess.stderr.on('data', (errData) => {
  console.error(`FFmpeg错误:${errData.toString()}`);
});

方案二:复用prism-media的解复用器与解码器

复用全局的解复用器和解码器,保留WebM容器的解析上下文:

const prism = require('prism-media');

// 全局初始化解复用器与解码器,仅创建一次
const webmDemuxer = new prism.opus.WebmDemuxer();
const opusDecoder = new prism.opus.Decoder({
  rate: 48000,
  channels: 1,
  frameSize: 960,
});

// 提前建立管道,监听解码后的PCM数据
webmDemuxer.pipe(opusDecoder).on('data', (pcmChunk) => {
  // 此处添加发送PCM至目标服务的逻辑
});

// 接收WebSocket分片,写入解复用器
ws.on('audioStream', (message) => {
  const buffer = Buffer.isBuffer(message) ? message : Buffer.from(message);
  webmDemuxer.write(buffer);
});

方案三:使用libav绑定(如node-av)

通过node-av调用libav库处理流式转码,需提前安装libav依赖:

const av = require('av');
av.registerAll();

// 创建流式输入
const inputStream = av.Input.fromBufferStream();
// 获取音频流
const audioStream = inputStream.streams.find(stream => stream.type === 'audio');

// 配置解码与输出格式
audioStream.codec.context.setCodec('opus');
audioStream.pipe(new av.format.S16LEAudio({ sampleRate: 48000, channels: 1 }))
  .on('data', (pcmChunk) => {
    // 此处添加发送PCM至目标服务的逻辑
  });

// 写入WebSocket接收的分片
ws.on('audioStream', (message) => {
  const buffer = Buffer.isBuffer(message) ? message : Buffer.from(message);
  inputStream.write(buffer);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 04:42:13