
使用 webcodecs api 直接编码 webgl 帧为 h.264,并手动构建 mp4 容器,是当前浏览器端最高效、原生支持、跨平台(含 android chrome)的视频生成方案。
在 Web 应用中将 WebGL 渲染画面实时录制为 MP4 视频,长期以来受限于性能与兼容性——wasm 版 ffmpeg 体积大、启动慢、移动端耗电高;老旧的第三方 MP4 封装库(如已归档的 wasm-mp4-encoder)缺乏维护,且在现代 android Chrome(v110+)中因 WebAssembly 线程或内存模型变更而失效。真正高性能、低延迟、可生产落地的解法,是拥抱浏览器原生能力:WebCodecs API + 手动 MP4 muxing。
✅ 核心优势:为什么 WebCodecs 是最优选?
- 零依赖、零加载:内置于 Chromium(Chrome / edge / Android Chrome)、firefox(部分支持)、safari(逐步推进),无需下载 MB 级 WASM 模块;
- 硬件加速直通:通过 hardwareAcceleration: ‘prefer-hardware’(需配合 latencyMode: ‘realtime’)可触发 GPU 编码器,帧率稳定、CPU 占用极低;
- 流式处理友好:VideoEncoder 支持异步 encode() + output 回调,天然适配逐帧捕获(readPixels → VideoFrame → 编码)流水线;
- Android 全面兼容:自 Chrome 94 起已在 Android 12+ 设备稳定支持,实测 Android 13(Chrome 124)完全可用。
? 实战三步走:从 canvas 到可播放 MP4
第一步:高效捕获帧并构造 VideoFrame
避免 readPixels 后冗余转换。推荐直接使用 canvas> 元素作为 VideoFrame 输入源(无需 ImageData 中转):
// 假设 yourWebGLcanvas 已渲染完毕 const frame = new VideoFrame(yourWebGLCanvas, { timestamp: performance.now() * 1000, // μs 精度时间戳 visibleRect: new DOMRect(0, 0, canvas.width, canvas.height) });
⚠️ 注意:确保 Canvas 的 alpha: false(WebGL 上下文创建时设置),否则透明通道会强制启用 BGRA 转换,降低性能。
第二步:配置并启动 VideoEncoder
关键配置决定质量与性能平衡:
const encoder = new VideoEncoder({ output: handleChunk, error: e => console.error("Encoder error:", e.message) }); encoder.configure({ codec: "avc1.42C01E", // H.264 Baseline Profile Level 3.0 (兼容性最佳) width: canvas.width, height: canvas.height, bitrateMode: "variable", bitrate: 2_000_000, // 2 Mbps(按需调整) framerate: 30, latencyMode: "realtime", // 必选!启用低延迟编码路径 avc: { format: "annexb" } // 输出带 NALU 起始码(00 00 00 01),MP4 muxing 必需 });
第三步:封装为 MP4 —— 手写原子(atom)而非依赖黑盒库
MP4 本质是“盒子嵌套”结构。最小可播放 MP4 至少需以下原子:
- ftyp(文件类型声明)
- moov(元数据:mvhd, trak, mdia, minf, stbl 等)
- mdat(实际 H.264 数据)
你无需实现全部——可复用成熟轻量库完成核心 muxing:
- ✅ MP4Box.js(推荐):专注 MP4 操作,支持 addTrack() + addFrame() 流式写入,自动处理 SPS/PPS 注入、PTS/DTS 计算、stss(关键帧表)生成;
- ✅ mux.js:Video.js 生态,API 简洁,适合简单场景;
- ❌ 避免自行手写全 MP4 结构(如原始答案中的 stss 示例),易出错且维护成本高。
使用 MP4Box.js 封装示例:
import MP4Box from 'mp4box'; const mp4boxFile = MP4Box.createFile(); mp4boxFile.enableLogging(false); // 添加 H.264 轨道(传入 SPS/PPS 及编码参数) const track = mp4boxFile.addTrack("video/h264", { width: canvas.width, height: canvas.height, timescale: 1000, bitrate: 2_000_000 }); // 在 handleChunk 中注入帧数据 function handleChunk(encodedFrame, metadata) { if (metadata.decoderConfig) { // 注入 SPS/PPS(仅首次) const sps = new Uint8Array(metadata.decoderConfig.description); mp4boxFile.setVideoTrackMetadata(track.id, sps, null); } // 添加帧(自动处理关键帧标记、时间戳) const sample = { data: encodedFrame, pts: encodedFrame.timestamp, // μs cts: 0, dts: encodedFrame.timestamp, is_sync: metadata.keyFrame || false, duration: 1000 / 30 * 1000 // 30fps → 33333 μs/frame }; mp4boxFile.addSample(track.id, sample); } // 结束后导出 Blob function exportMP4() { const buffer = mp4boxFile.flush(); const blob = new Blob([buffer], { type: "video/mp4" }); const url = URL.createObjectURL(blob); // 下载或播放... }
? 关键注意事项
- 内存管理:每次 VideoFrame 使用后务必调用 .close(),否则导致内存泄漏;
- 帧率控制:WebGL 渲染帧率可能高于目标视频帧率(如 60fps → 30fps),需主动丢帧(检查 encoder.encodeQueueSize > 2);
- SPS/PPS 处理:H.264 编码器首帧输出包含 SPS/PPS,必须在 MP4 的 moov 中正确写入,否则无法播放;
- 移动端限制:Android Chrome 对 VideoEncoder 的 bitrate 上限较保守(建议 ≤ 4Mbps),过高会导致编码失败;
- 回退方案:对不支持 WebCodecs 的浏览器(如 Safari
✅ 总结
放弃臃肿的 WASM FFmpeg,拥抱 WebCodecs 是浏览器端视频编码的范式升级。它以原生性能、低延迟、高兼容性,成为 WebGL 录屏、实时可视化导出、Web 游戏录像等场景的首选技术栈。结合 MP4Box.js 这类专业 muxing 库,你能在 200 行核心代码内,构建出媲美桌面软件的 Web 视频生成能力——轻量、可靠、面向未来。