基于WebCodecs的视频编辑器:如何向FFmpeg.wasm传入编码块混流
用FFmpeg.wasm处理WebCodecs编码块的混流方案
核心思路
WebCodecs输出的EncodedVideoChunk/EncodedAudioChunk本质是带编码信息的二进制数据,FFmpeg.wasm需要先把这些数据转成它能识别的原始码流文件(比如H.264裸流、AAC裸流),再通过FFmpeg命令完成混流。
步骤实现
安装FFmpeg.wasm依赖
先安装核心包:npm install @ffmpeg/ffmpeg @ffmpeg/core实现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 = []; } }使用示例
// 假设你已经有了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
相关产品推荐
相关产品推荐

