基于WebCodecs的网页端高性能视频截帧

2,196 阅读9分钟

本期作者

截屏2024-01-29 18.30.14.png

业务介绍

web投稿页是B站的主要投稿来源,有很多高粉UP主使用web端进行投稿。

封面部分是投稿过程中耗时占比较高的步骤,因此在过去,web投稿页已上线了自动的封面截取&推荐功能,有效提升了用户体验。同时在此过程中有了一定的技术积累。

自动封面功能依赖于对用户上传视频进行截帧的能力,最简单的方式是在上传完成之后由服务端进行视频截帧并返回推荐的候选封面,但显然这一步会有大量的等待时间,因此我们采用的是纯前端视频截帧能力。

实际上在web投稿页有多处需要截帧的地方:

  • 封面推荐:截取多张低清图在前端进行AI打分,基于打分结果截取最多10张高清图供UP主选择

  • 封面选帧:对默认推荐的帧不满意,手动获取准确时间点的帧画面

  • 分区&话题推荐:从视频中截取多帧,打包上传至后台进行分析后返回推荐结果

图片图片

过去方案

过去web投稿页采取两套视频截帧方案,wasm优先,canvas兜底

Video + CanvasWebAssembly + FFmpeg
流程- Video标签 --> 设置video时间
  • canvas context 2d, drawImage()直接成图 | FFmpeg API调用+数据传递为主- 视频文件解封装

  • 逐个读取关键帧图像数据

  • 数据层层传递

  • web端进行图像渲染(webgl / canvas) | | 优点 | - 开发简单:利用浏览器内部的视频播放器能力 | - 视频支持性好:几乎支持所有市面上可见的视频格式(行业标杆) | | 缺点 | - 无法进行错误处理,有时会黑屏,但不报错

  • 不同浏览器有形态各异的表现,速度和可用性难以保证

  • 播放器本身的缓存或预加载等机制带来性能浪费 | - 性能损耗大:相比canvas截帧慢;

  • 内存消耗大:早期的wasm功能甚至会导致页面崩溃;

  • 开发门槛高,需要了解ffmpeg lib的使用方式,要写C代码,需要手动构建各类基础库 |

现状:截帧成功率97%左右,封面推荐耗时(去掉极端数据)

  • 平均:8.4s

  • 50分位:16s

  • 90分位:19s

WebCodecs是什么

WebCodecs于21年9月份推出,是用于在web页面上对音视频进行底层操纵(如编解码)的API。

WebCodecs是相对底层的API,准确来说是为有音视频开发基础的人准备的,对前端同学来说有一定的门槛。

在使用FFmpeg时可直接调用包装好的方法,主要门槛在于wasm环境的配置和构建。而使用WebCodecs时则需要基于编解码的原理手动实现功能。或许后续WebCodecs将会推出更加上层的API。

所以在进一步介绍WebCodecs截帧方案之前,我想先介绍一些视频处理的入门知识,感兴趣的可以参考附录中的链接进一步学习。

MP4的入门知识

视频处理的基本概念

图片

编码/解码:

  • 视频的编码是将原始的图像信息进行变换压缩等处理,方便传输并保证图像质量。解码则是将压缩后的文件还原成视频需要的一连串图像
  • 常见的编码格式: H.265; mpeg4; vp9 ……

封装/解封装:

  • 一个视频文件可能包含多个音频和视频流,通过封装格式将他们聚合在一起,在使用时按照规则逐步解析
  • 常见的封装格式:mov,mp4,m4a,3gp,3g2; matroska; flv; avi ……

在这里简单介绍下.mp4文件常用的h264编码以及MP4封装

编码-帧内编码(以JPEG图片压缩算法为例)

利用人眼的生物特性结合数学方法进行数据压缩,并确保图片质量。主要步骤:

图片

具体流程在这就不展开了,总之,经过压缩后图片的文件大小将有非常显著的缩小

图片

⬇️

图片

原图大小:162010803/1024/1204 = 4.25MB ----> 编码后大小:856KB

PS:效果仅供参考,两者皆为经过JPEG压缩的图片,只不过压缩比不同

编码-帧间编码

尽管经过帧内编码的压缩,图片已经有了很明显的体积减少,但存储视频的每一帧是依然是很不明智的行为。因此需要帧间编码。

通常有两种方式进行帧间编码:动态补偿+帧间差异

动态补偿

图片

通常,两个连续的帧之间是存在相同部分的,只是位置发生了变化因此可以通过存储 块的索引 + 偏移量(向量)以减少存储体积

帧间差异

仅有动态补偿还不够还原每一帧的画面,还需要通过两帧之间的diff帧来辅助还原

图片

diff帧的画面通常信息量比较低,因此通过帧内压缩会获得很高的压缩比

使用这两种方法,结合上一帧参考帧,便可以获得当前帧了

图片

不同的帧类型

对应的,产生了三种帧类型

I 帧:俗称的关键帧,仅使用了帧内编码,可以被独立还原为图像

P帧:帧的图像还原依赖前一帧的解码结果

B帧:帧的图像还原依赖前一帧与后一帧的解码结果

图片

帧的展示顺序与解码顺序可能是不一样的

封装

MP4封装文件基本结构:所有数据存放在box中

图片

WebCodecs截帧方案

设想一个问题:只使用一个编程语言的基本API,如何最高效地获取一个.mp4文件中的某一个时间点所在的图像?

在了解了上面的基本知识后,我们可以分4步解决这个问题:

图片

不同于播放器:截帧不需要预解码缓存等步骤。为了保证性能,需要多少数据拿多少,拿多少处理多少,避免多余的文件读取和解析造成性能和内存的浪费。

元数据读取&解析

  1. 读取文件头部8byte的数据,按照box的header规则逐个获取各box的位置以及大小

图片

PS:moov可能在文件的末尾,顺序不固定

  1. 将moov box所在文件块切片,提供给解封装器解析,获取到:
  • 该视频的详细编码参数

  • 所有帧的索引信息

图片

寻帧

策略:帧的时间戳并不是连续的的 → 某个时间点对应的帧可能并不存在 → 使用距离最近的帧

图片

获取到最近的关键帧和非关键帧之后,则要根据截帧的需求提供不同的文件块给解码器解码

只提供关键帧速度更快,适合精度不高的场景(封面推荐),准确截帧适合精度要求高的场景(封面选帧)

整体过程

由于解封装器(mp4box.js)和解码器(WebCodecs-VideoDecoder)本身为流式设计,优先服务于流式的应用场景(如直播视频流,点播视频流,需要通过网络请求分块获取到文件内容)。而视频截帧是一个本地场景,已经有了完整的文件。且视频截帧的API最好是类似同步的方式,在一个方法调用中完成所有的帧截取,并一起返回。

因此设计了通过事件抛出以及定时器机制以达到对内部流式依赖库的包装。

同时将计算密集的解封装、解码、渲染工作挡在独立的web worker中执行,确保宿主页面运行流畅不受影响。

图片

性能分析

本地测试:

测试机上模拟了web投稿页场景,对WebCodecs / WebAssembly / Canvas 三种截帧方式的性能进行了测试。

图片

测试样本:720p视频2个,1080p视频3个,2k视频1个,4k视频3个

测试环境:2020 M1 MacBook pro, 公司测试windows本(i5-1135G7 1.38~2.40GHz)

测试方式:在不同测试机上对每个视频跑三次测试用例,共81次

测试用例:模拟web投稿页截帧流程,数量,分辨率保持相同

实际场景中:视频的编码,分辨率,压制参数等都会对截帧性能有影响,在这里以分辨率进行粗略的分类

线上数据:

图片图片

总结:

  • 随着视频规格的提升,webcodecs的截帧速度为wasm和canvas的 2.5~5 倍

  • 提前 3~13s 完成页面所需的截帧任务,用户能够更快的看到推荐结果

  • 在内存消耗上有一定的降低

WebCodecs截帧方案的优点&缺点

优点

  • 速度很快,受视频规格影响小
  • 读取文件少
  • 内存占用有一定降低,且表现稳定

缺点

  • 依赖解封装器的实现,当前使用了mp4box.js作为解封装器,约能覆盖95%的视频
  • 目前仅mp4和webm的解封装器较完善
  • WebCodecs浏览器支持性一般,当前为85%左右

规划

  • 作为web投稿页首选截帧方式,根据线上表现做进一步优化

  • 其他封装格式的视频支持:支持webm封装格式(已支持,且开源了mkv demuxer)

  • 开源

附录

jpeg压缩算法介绍:

视频编码介绍:www.youtube.com/watch?v=QoZ…

不同的帧类型:I, P, and B-frames - Differences and Use Cases Made Easy - OTTVerse(ottverse.com/i-p-b-frame…

codec string的含义([avc1.4d0033]代表什么):Codecs in common media types - Web media technologies | MDN(developer.mozilla.org/en-US/docs/…

MP4封装类型介绍:mp4封装格式各box类型讲解及IBP帧计算 - 知乎(zhuanlan.zhihu.com/p/457888765…

在线MP4解析工具:Online Mp4 Parser(www.onlinemp4parser.com/)

WebCodecs官方说明:WebCodecs(w3c.github.io/webcodecs/#…

WebCodecs代码示例:github.com/w3c/webcode…