Yard


聊聊webrtc的hevc解码那些事儿

1 HEVC 是什么?

高效率视频编码(High Efficiency Video Coding,简称 HEVC),又称为 H.265 和 MPEG-H 第 2 部分,是一种视频压缩标准,被视为是 ITU-T H.264/MPEG-4 AVC 标准的继任者。2004 年开始由 ISO/IEC Moving Picture Experts Group(MPEG)和 ITU-T Video Coding Experts Group(VCEG)作为 ISO/IEC 23008-2 MPEG-H Part 2 或称作 ITU-T H.265 开始制定。第一版的 HEVC/H.265 视频压缩标准在 2013 年 4 月 13 日被接受为国际电信联盟(ITU-T)的正式标准。HEVC 被认为不仅提升影像质量,同时也能达到 H.264/MPEG-4 AVC 两倍之压缩率(等同于同样画面质量下比特率减少到了 50%),可支持 4K 清晰度甚至到超高清电视(UHDTV),最高清晰度可达到 8192×4320(8K 清晰度)。

总结一下,HEVC 又称 H265,是比 H264 更高效的视频压缩模式。这里就不详细介绍 HEVC 是如何处理视频的,只是来聊聊在前端是如何去实现 HEVC 格式的视频播放。

2 HEVC 在前端的支持情况

这里是 caniuse 网站上显示的对 HEVC 的支持情况 https://caniuse.com/?search=hevc

可以看到市面上 90%左右的浏览器都不支持 HEVC 的格式。

所以我们需要自己去解码以及渲染音视频。

3 WebRTC 传输自定义数据

h264 的视频,因为浏览器默认支持这个格式,所以我们可以直接用浏览器内置的实现去处理音视频。 最后能直接拿到音视频的 track,然后交给 video 模块去处理。

在 HEVC 下,我们需要用到 WebRTC 的 dataChannel 的能力,去传输自定义的音视频数据包。 https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel

4 HEVC 格式视频的编码和解码

音视频方面的编码和解码当然跑不出 ffmpeg 的范畴,这里的编码和解码都由 ffmpeg 模块来处理。

https://ffmpeg.org/

5 如何在前端使用 ffmpeg

有一个 js 的 ffmpeg 实现版本

https://github.com/Kagami/ffmpeg.js/

但是考虑到 js 的对运算密集型的任务处理效率低下,所以我们将使用 wasm 标准来将 ffmpeg 模块直接编译成汇编语言,在前端运行。

https://webassembly.org/

那么如何编译呢? emcc 就集成了这个工具,可以将 C 语言编译成 wasm 模块。 https://emscripten.org/docs/compiling/WebAssembly.html?highlight=wasm

编译完成后,我们就可以在前端通过 wasm 调用经过 emcc 优化过的 c 代码。

为了防止主线程被阻塞,我们将 wasm 的运营放到 WebWorker 内运行。

这样就可以拿到解压缩之后的音视频文件。

6 HEVC 视频渲染

因为浏览器支持的原因,所以我们需要自己去渲染视频,那么如何渲染呢?

这里就有个大杀器:webGL。

webGL 渲染视频的详细内容就不在这里详细说明了,webGL 设计的内容比较庞大,感兴趣的小伙伴可以自己了解下。

7 HEVC 音频渲染

既然视频自己渲染了,音频也得自己去做处理。

这里应用到的是浏览器提供的 AudioContext API

https://developer.mozilla.org/en-US/docs/Web/API/AudioContext

AudioContext 的详细实现也不在这里赘述了,简单来讲就是把一段段的音频数据交给 AudioContext 去播放。

8 音视频同步

一旦自己去实现了音视频的渲染,那么就一定需要考虑音视频同步的问题。

音视频同步的底层是依赖于数据的 pts 参数(用来表示音视频的时间概念)

目前音视频同步的主要方案有以下三种:

  1. 视频同步到音频。
  2. 音频同步到视频。
  3. 音视频同步到外部时钟.

而最常用的方案一般是“视频同步到音频”。

因为音频是流式的,按照规律的匀速的速率去播放,才能显得更加 “平滑”,而视频的播放其实是一张一张图片进行刷新显示,它的刷新时间的调整相对而言更容易一些,用户肉眼的敏感度也更弱一些。

以下是音画同步在人体感知上的一个参考值:

  1. 无法察觉:音频和视频的时间戳差值在:-100ms ~ +25ms 之间
  2. 能够察觉:音频滞后了 100ms 以上,或者超前了 25ms 以上
  3. 无法接受:音频滞后了 185ms 以上,或者超前了 90ms 以上

我们在考虑同步的时候一定要参考上面的值,这样才能使用户的体验更加舒适。

9 CPU 占用

完成这整一条链路之后,在实际的使用场景中,发现 CPU 的占用非常高(这就是所谓的软件解码,CPU 本身就不擅长处理音视频相关的任务。)

单个视频的解码将占用 CPU 单核 30%左右的 usage。

当同时查看四个视频时, CPU 单核的使用以及超过了 100%,但是依旧是单核在运行,而没有用到其他的几个核心,由此可见,Chrome 的 WebWorker 虽然是多起了一个 js 线程,防止阻塞主进程,但是对于 WebWorker 并没有进行多核优化。

10 Electron and C++ addon 进行多核处理优化

Electron 是一个可支持 node 进程的客户端,我们可以在 Electron 内部用 nodejs 的子进程模式去利用当前 CPU 的多核特性,将原本在单核内的任务放到多核中去运行,提高渲染效率。

在前端我们使用 wasm 来使用 ffmpeg 模块,那么在 node 端, 我们要如何去调用呢?

有个 nodejs 支持的模块叫做 C++ addon,可以实现在 nodejs 内调用 C/C++的内容 https://nodejs.org/api/addons.html

编译由node-gyp来完成。

11 终极硬件解码优化

上面虽然开启了多线程优化,但是解码效率还是不够,最好的模式就是开启GPU(天生就是为了处理音视频相关的任务)渲染。这个就需要底层C模块的支持。

以上就是前端的HEVC视频渲染方案以及优化方案。