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

基于WebCodecs的视频编辑器:如何向FFmpeg.wasm传入编码块混流

用FFmpeg.wasm处理WebCodecs编码块的混流方案

核心思路

WebCodecs输出的EncodedVideoChunk/EncodedAudioChunk本质是带编码信息的二进制数据,FFmpeg.wasm需要先把这些数据转成它能识别的原始码流文件(比如H.264裸流、AAC裸流),再通过FFmpeg命令完成混流。

步骤实现

  1. 安装FFmpeg.wasm依赖
    先安装核心包:

    npm install @ffmpeg/ffmpeg @ffmpeg/core
    
  2. 实现Muxer_FFmpeg类
    下面是完整的类实现,包含编码块转换、虚拟文件写入、混流执行的逻辑:

    import { FFmpeg } from '@ffmpeg/ffmpeg';
    import coreURL from '@ffmpeg/core/dist/ffmpeg-core.js?url';
    
    class Muxer_FFmpeg {
      #ffmpeg;
      #videoChunks = [];
      #audioChunks = [];
      #videoCodec; // 比如'h264'、'vp9'
      #audioCodec; // 比如'aac'、'opus'
    
      constructor(videoCodec = 'h264', audioCodec = 'aac') {
        this.#videoCodec = videoCodec;
        this.#audioCodec = audioCodec;
        this.#ffmpeg = new FFmpeg();
      }
    
      // 初始化FFmpeg实例
      async init() {
        if (!this.#ffmpeg.isLoaded()) {
          await this.#ffmpeg.load({ coreURL });
        }
        // 可选:开启日志输出,方便调试
        this.#ffmpeg.on('log', ({ message }) => console.log(message));
      }
    
      // 添加编码块到缓存
      addVideoChunk(chunk) {
        this.#videoChunks.push(new Uint8Array(chunk.data));
      }
    
      addAudioChunk(chunk) {
        this.#audioChunks.push(new Uint8Array(chunk.data));
      }
    
      // 执行混流
      async mux(outputFileName = 'output.mp4') {
        // 1. 将缓存的编码块拼接成完整的码流二进制
        const videoBuffer = new Uint8Array(this.#videoChunks.reduce((acc, chunk) => acc + chunk.length, 0));
        let offset = 0;
        for (const chunk of this.#videoChunks) {
          videoBuffer.set(chunk, offset);
          offset += chunk.length;
        }
    
        const audioBuffer = new Uint8Array(this.#audioChunks.reduce((acc, chunk) => acc + chunk.length, 0));
        offset = 0;
        for (const chunk of this.#audioChunks) {
          audioBuffer.set(chunk, offset);
          offset += chunk.length;
        }
    
        // 2. 写入FFmpeg虚拟文件系统
        await this.#ffmpeg.writeFile(`input.${this.#videoCodec}`, videoBuffer);
        await this.#ffmpeg.writeFile(`input.${this.#audioCodec}`, audioBuffer);
    
        // 3. 执行FFmpeg混流命令(直接复制编码流,避免重新编码)
        await this.#ffmpeg.exec([
          '-i', `input.${this.#videoCodec}`,
          '-i', `input.${this.#audioCodec}`,
          '-c', 'copy', // 关键:直接复制已有编码,提升速度
          outputFileName
        ]);
    
        // 4. 读取输出文件
        const outputData = await this.#ffmpeg.readFile(outputFileName);
        return new Blob([outputData], { type: 'video/mp4' });
      }
    
      // 清理资源
      async destroy() {
        await this.#ffmpeg.terminate();
        this.#videoChunks = [];
        this.#audioChunks = [];
      }
    }
    
  3. 使用示例

    // 假设你已经有了encodedVideoChunks和encodedAudioChunks数组
    const muxer = new Muxer_FFmpeg('h264', 'aac');
    await muxer.init();
    
    // 批量添加编码块
    encodedVideoChunks.forEach(chunk => muxer.addVideoChunk(chunk));
    encodedAudioChunks.forEach(chunk => muxer.addAudioChunk(chunk));
    
    // 执行混流,获取输出Blob
    const outputBlob = await muxer.mux();
    // 可以下载或预览
    const url = URL.createObjectURL(outputBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'edited-video.mp4';
    a.click();
    
    // 最后清理资源
    await muxer.destroy();
    

关键注意事项

  • 编码格式匹配:WebCodecs的编码配置要和FFmpeg支持的输入格式对应,比如如果用VP9编码视频,要把输入文件后缀设为.vp9,FFmpeg会自动识别。
  • 性能优化:如果编码块数量极大,不要一次性拼接所有块,可考虑分批次写入虚拟文件(FFmpeg支持追加写入,但需要确保码流的连续性)。
  • FFmpeg命令调整:如果遇到时间轴不同步,可能需要添加-async 1或调整时间基参数,比如-video_track_timescale、-audio_track_timescale。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 00:25:19