# Web 音视频(五)在浏览器中合成视频

Web 音视频目录

经过前序章节的介绍,读者能大致了解如何在播放器中解析、创建视频;
本章介绍何在浏览器中合成视频,这是视频编辑中最基础的功能。

你可以跳过原理介绍,直接查看 WebAV 合成视频示例

# 在视频上叠加素材

常见的素材有:视频、音频、图片、文字

在浏览器中创建视频章节介绍了,视频编码器只接受 VideoFrame 对象,而 canvas 可以构造 VideoFrame;

在视频上叠加素材的实现原理:视频 + 素材 -> canvas -> VideoFrame -> VideoEncoder

  1. 先绘制视频到 canvas,再绘制其他素材
  2. 使用 canvas 元素构造 VideoFrame 对象
  3. 使用编码器编码 VideoFrame
  4. 处理下一帧

音频则是将各个素材的音频数据(如果有)相加即可,详情可查看上一章在浏览器中处理音频

视频是由一帧帧图像在时间轴上排列而成,原视频也视为一个普通素材;
所以问题可以简化为:决定某一时刻分别需要绘制哪些素材第几帧,时间轴从 0 开始,重复以上步骤就能得到一个新视频。

# 实现步骤总结

  1. 将素材抽象为 Clip 接口,不同素材有不同实现,如 MP4ClipImgClip
  2. 创建一个 Combinator 对象控制时间轴,向各个素材(Clip)发送时间信号,首次为 0 时间不断增加,增加的步长取决于最终需要合成视频 FPS,step = 1000 / FPS ms
  3. 素材由接收到的时间值,决定当前时刻需要提供的数据:自身的第几帧图像、音频片段(Float32Array
  4. Combinator 收集并合成各个素材的图像(绘制到 canvas)、音频(Float32Array相加)数据
  5. Combinator 将合成的数据转换成 VideoFrame、AudioData 传递给编码器,编码(压缩)后封装到对应格式的视频容器格式
  6. Combinator 增加时间信号的值,重复步骤 2~5

# 素材抽象设计(Clip)

素材分为动态(视频、音频、动图)与静态(图片、文字)两种,静态素材不受时间影响比较简单,接下来以视频素材举例。

Clip 接口简化实现

export interface IClip {
  /**
   * 当前瞬间,需要的数据
   * @param time 时间,单位 微秒
   */
  tick: (time: number) => Promise<{
    video?: VideoFrame | ImageBitmap;
    audio?: Float32Array[];
    state: 'done' | 'success';
  }>;

  ready: Promise<{ width: number; height: number; duration: number }>;
}

MP4Clip (opens new window) 实际源码有两百多行,限于篇幅,这里只介绍原理

  1. 使用 mp4box.js 解封装、WebCodecs 解码视频,得到 VideoFrame、AudioData
  2. 从 AudioData 提取 PCM 数据(Float32Array)
  3. MP4Clip 内部使用数组管理图像(VideoFrame)与音频数据(Float32Array)
  4. Combinator 调用 MP4Clip.tick 时,根据事件参数找到对应的图像帧与音频切片并返回

WebAV 提供的其他 Clip (opens new window)

# Combinator 设计

提前介绍一下 OffscreenSprite,将 Clip 使用 OffscreenSprite 包装起来,记录坐标、宽高、旋转等属性,用于控制素材在 canvas 的位置、实现动画等;在下一篇文章介绍,本章略过。

Combinator 的核心逻辑

class Combinator {
  add(sprite: OffscreenSprite) {
    // 把 sprite 管理起来
  }

  output() {
    let time = 0;
    while (true) {
      let mixedAudio;
      for (const spr of this.sprites) {
        const { video, audio, state } = spr.tick(time);
        // 伪代码,实际是对 Float32Array 的元素循环相加,详情查看【编码封装音频数据】章节
        mixedAudio += audio;
        ctx.draw(video);
      }
      // 伪代码,具体构造 VideoFrame AudioData 方法查看前序章节
      // 将 VideoFrame AudioData 传给编码器,查看前序章节
      new VideoFrame(canvas);
      new AudioData(mixedAudio);
      // 输出的目标视频 30 FPS,因为单位是微秒,所以乘以 1000
      time += (1000 / 30) * 1000;
    }
  }
}

完整源码 (opens new window)

# 拼接视频

前后拼接视频有两种方式

  1. 重编码拼接,特征是输出视频速度慢、兼容性好
    原理跟上一步合成视频是一样的,两个素材的结束、开始时间正好衔接,重新绘制 canvas 再编码
  2. 快速拼接(非重编码),特征是速度快、可能出现兼容性问题
    原理是拆开视频容器,复制其中的编码数据到新的容器中,仅修改其时间偏移

这里讲解快速拼接的核心代码

// SampleTransform 将 mp4 文件流转换为 MP4Sample 流
// 由 autoReadStream 读取流,给回调函数 MP4Sample
autoReadStream(stream.pipeThrough(new SampleTransform()), {
  onChunk: async ({ chunkType, data }) => {
    const { id: curId, type, samples } = data;
    const trackId = type === 'video' ? vTrackId : aTrackId;

    samples.forEach((s) => {
      outfile.addSample(trackId, s.data, {
        duration: s.duration,
        // offsetDTS offsetCTS 是上一个片段的结束时间
        // 此处复用 data,仅修改时间偏移 所以速度很快
        dts: s.dts + offsetDTS,
        cts: s.cts + offsetCTS,
        is_sync: s.is_sync,
      });
    });
  },
});

完整源码 (opens new window)

# WebAV 合成视频示例

DEMO 链接在附录,可在线立即体验

在视频上叠加图片

const resList = ['./public/video/webav1.mp4', './public/img/bunny.png'];

const spr1 = new OffscreenSprite(
  'spr1',
  new MP4Clip((await fetch(resList[0])).body!)
);
const spr2 = new OffscreenSprite(
  'spr2',
  new ImgClip(await createImageBitmap(await(await fetch(resList[1])).blob()))
);
const com = new Combinator({
  width: 1280,
  height: 720,
  bgColor: 'white',
});

await com.add(spr1, { main: true });
await com.add(spr2);
// 返回新的 MP4 文件流
com.output();

快速合并 MP4 文件

const resList = ['./public/video/webav1.mp4', './public/video/webav2.mp4'];
// 新的 MP4 文件流
const stream = fastConcatMP4(
  await Promise.all(resList.map(async (url) => (await fetch(url)).body!))
);

# 附录

💗 博主正处于裸辞待业状态,欢迎 商务合作 💗

相关文章

WebAV SDK(Web 视频编辑)V1 发布

前言 WebAV 是基于 WebCodecs 构建的 SDK,用于在 Web 平台上创建/编辑视频文件。 V1 对项目来说是里程碑版本,意味着 API 已经稳定,且功能的稳定性也经过了长时间的考验,可用于生产环境。 我在 20 年加入 B 站,开始接触 Web 音视频相关的知识(Web 播放器), WebCodecs API 在 21 年发布; 我预期 WebCode ...

纯 Web 视频剪辑

前言 WebCodecs API 为 Web 平台提供了音视频编解码能力,使得在 Web 平台(网页、Electron)上实现高效、专业的视频剪辑成品成为可能。 读者可阅读笔者的入门系列文章获取更详细的信息,或直接使用 WebAV 开源项目在浏览器中创建/编辑音视频文件。 背景 & 方案 为了解决主播投稿场景中,需要对直播视频进行简单编辑的诉求, ...

Google IO 分享 WebCodecs、OPFS 文字版

背景 2024 北京 Google I/O 邀请我参加合作者开发者论坛,主题是 "Build powerful Web App"; 笔者近期在公司项目中实践 WebCodecs,对应的开源项目是 WebAV,在 Web 音视频领域算是相对前沿的探索; 本文主要分享基于 WebCodecs、OPFS 实现的视频剪辑产品,探讨这些 API 还有哪些应用场景; W ...

WebCodecs 性能表现及优化思路

笔者开源 WebAV 已经一年半,还写了系列文章帮助初学者入门 Web 音视频。 之前一直隐隐担心在 Web 平台处理音视频与 Native APP 会有明显性能差距,因为 WebCodecs API 毕竟被浏览器代理了一层,且一些数据处理需要 js 配合,不确定有多大的性能损耗。 相信刚接触 WebCodecs 的读者也非常关心它的性能表现如何。 ...

WebCodecs 编码字符串(codec)介绍

笔者的 Web 音视频系列文章 视频播放经常会看到这样的字符串 video/mp4; codecs="avc1.4d002a",WebCodecs 编解码器初始化也需要配置 codec 参数,本文尝试简单介绍编码字符串含义。 视频编码分许多种类,每个种类还分多个版本,不同种类、版本对应的编解码算法、支持的能力(分辨率上限、色深等等)不同 ...

WebCodecs 开启 Web 音视频新篇章

你可以先略过下面的无聊文字,体验一番 WebCodecs 的实力 WebCodecs 是什么 WebCodecs 是一个 Web 规范,21 年 9 月份在 Chrome 94 中实现 WebCodecs 提供访问编解码能力的接口,可精细控制音视频数据 Web 音视频 API 存在什么问题 音视频技术在 Web 平台上的应用非常广泛,已有许多 Web ...

【译】WebCodecs 说明

本文翻译至 WebCodecs Explainer 问题与动机 已有许多 Web API 在内部使用媒体编解码器来支持特定用途,比如: HTMLMediaElement and Media Source Extensions WebAudio (decodeAudioData) MediaRecorder WebRTC 但是还没有一种通用的方式来灵活 ...

Web 音视频(六)图像素材处理

Web 音视频目录 前序章节介绍了如何在浏览器中解析、创建视频,以及给视频添加一些自定义素材(图片、音频、文字...); 本章介绍如何给图像素材加特效、加动画,实现转场、移动水印、图像滤镜美化等功能。 你可以跳过原理介绍,直接查看 WebAV 示例 素材动画 在视频制作中实现动画跟其他场景略有不同,因为视频 ...

Web 音视频(七)中期回顾

Web 音视频目录 个人回顾 本系列更新至今,持续了一个月时间,内容可以总结为音频、视频数据的 解析 - 处理 - 合成,在浏览器中比较粗粒度地实现音视频编辑的主要环节,差不多是把 WebAV 项目的原理讲完了。 讲解的知识非常浅,目标读者是准备在 Web 平台进行音视频开发的新手; 这是我第一次进行高频率技术写作,将一 ...

Web 音视频(四)在浏览器中处理音频

Web 音视频目录 为什么单独介绍音频处理? 网络上缺乏音频处理的资料,绝大多数示例都是针对视频而略过音频,很多人在网上寻找音频处理的示例 对前端开发者来说,音频处理相对视频略微复杂一些 所以,本文专门针对音频数据,汇总讲解采集-处理-编码-封装全过程,帮助初学者入门。 ![audio-data-flow](./audio ...