Web 音视频开发趟坑指南

作者| 阿里文娱前端技术专家 归影

这不是一篇基于 MSE 开发 Web 播放器的入门文章,而是围绕 Web 播放器开发遇到的常见 问题与解决方案,毕竟入门文章常有而趟坑干货不常有。如果您有 Web 播放开发经验和音视频 技术基础,读起来会更有共鸣。

一、Web 播放器开发基础知识

先介绍 Web 播放器开发的一些基础知识。有人要问了,Web 播放器开发难道不是一个 video 标签就够了么?非也!

1.浏览器 Video 支持的格式非常有限

在 W3C 的标准里面 Video 只支持 MP4 格式 准确的说是 ISOBMFF(Fragment MP4)。当然 chrome 支持 WEBM,safari 支持 HLS(MPEG-TS)这都是自家的私有实现,做不得数。

2.浏览器 Video 无法逐个加载视频切片

现在主流的流媒体点播/直播技术,都会把视频切片。而 video 标签 src 只能挂载整个 MP4
资源。没法逐个的加载视频分段。
所以我们的主角出场—— MediaSource Extenstion,简称 MSE,是一套能不断的把音视频 二进制数据塞给 video 标签播放的 API。

Web 音视频开发趟坑指南

(图 1:MSE 简明结构)

MSE 内部可以创建一系列的 sourcebuffer,一般是一个音频 buffer,一个视频 buffer。把 MSE 做成 blob url 之后绑定给 video 的 src。然后就可以通过appendBuffer 往 video 里追加音视频数据了。有了 MSE,播放器器的整体结构是什么样的呢,见下图。

Web 音视频开发趟坑指南

(图 2:Web 播放器简明结构)

首先在浏览器层面,主要使用 video 标签、MSE、XHR 和 UI。
那么播放器主要由 Manager 驱动加载视频的playlist(比如 HLS 里的 m3u8,dash 里的 MPD, FLV 虽然不是 playlist 概念,但是是原理上差别不大,都是为了拿到视频的一个个的片段的地址), 并通过数据服务加载这一个个的分片。然后通过 transmuxer 也就是所谓的转封装器,把分片的 封装格式比如 TS 拆开(demux) 把连原始的音视频数据解出来,再重新打包成 fmp4(remux), 最后通过 MSE API 喂给 video 标签里,让 video 去播放。
因此播放器所做的事情最主要有两点:
1)转封装。即将 video 不支持的封装格式转码成 video 所支持的封装格式;
2)如何驱动整个播放进行。即决定何时下载下一个分片,何时需要解码插入到 video 的 buffer 里。

二、时间戳对齐

转封装除了的封装格式的解复用(demux)和再复用(remux)之外最重要的环节就是分片的 时间戳对齐策略,以及音视频同步。

Web 音视频开发趟坑指南

图 3(传说中的“开局一张图 原理全靠猜”)

简单讲一下上图:
红色代表音频的时间轴。蓝色/青色是视频的时间轴。PTS(Presentation Time Stamp) 指的是 这一帧需要渲染的时间。 DTS(Decoding Time Stamp) 指的是这一帧需要解码的时间。

1.首片首帧的对齐策略

正常来说音频 PTS 和 DTS 是一样的,而视频如果有 B 帧的话 DTS 往往要比 PTS 早一些(因 为要预留一定的时间解码)。因此视频的首帧会有一个洞(gap/shift 随便你怎么叫),如果不经处理插到 video 里,那么 video 里的 buffer 也会呈现出一小段的洞,一般是 0.08s(比如 10s 的分片 插 进去可能出现 0.08~10.08 的情况)。现在主流的做法是削掉这个洞。就是把 DTS 跟 PTS 强行拉 平,一般来说 chrome 不会出现太大的问题。但是 safari 不行,如果不预留一定的 DTS/PTS 偏 移,safari 前两帧的播放会明显卡顿。

2.后续对齐策略

后续分片的对齐,会通过 DTS/PTS 两个尾部指针来做。如果发现后续分片时间轴有间隔就 往前推从而填上间隔。如果发现重叠,就把重叠帧后移。这样虽然会导致后续分片的前几帧重叠。但在播放的过程中几乎没有影响。

三、音视频同步

首先,什么情况下会导致音画不同步?
1)视频源流压根没对齐。没救了,看下一点。
2)还是因为有洞。很多时候视频切出来的每个分片之间都不一定是严丝合缝的,分片间的 音视频时间戳可能有洞。而且对于 TS 由于音频每一帧的duration(≈23ms) 跟视频每一帧的duration(40ms@25fps) 无法吻合(整除) 所以加剧了这种参差不齐的情况。 那么,重点来了!chrome 有个特殊的机制,如果发现音频之间有洞之后,为了保证音频的平顺, 会自动把后续音频往前推抹平这个洞。如果每个分片都有洞,悲剧了,这种往前推的操作就会 积累越来越多导致音视频不同步。
小 tips:
打开 chrome 的媒体调试页面 chrome://media-internals 可以看到媒体播放相关的所有 debug 信息和 error 信息非常有用。其中就会有一条关于音频处理的提示:
当然这条显示的具体原因是自动切掉重叠 overlap 导致的。其实 gap/overlap 本质是一样的。 怎么办?当然是播放器自己主动把洞填上。具体做法是插帧。目前主要是插静音帧,或者复制 前一帧。静音帧会带来毛刺音,复制帧会导致拖音。我们目前的优化方案是判断附近的音频数 据量,数据量大时说明此处声音丰富(其实不算靠谱,姑且这么处理,因为没有更好的判断方式), 如果插静音帧会毛刺很明显,所以此时用复制帧,反之插静音帧。

四、那些年我们躺过的坑

1.不同版本表现差异 容忍度不同

1)Chrome 35 分水岭。chrome35 之前要求关键帧之后的第一帧 dts 不允许跟关键帧 dts 相 同,否则抛错。
2)低延迟的模式。把转封装出来的 FMP4 中的视频轨 duration(tkhd box) 设置成 0xffffffff 时 会让 chrome 认为这是直播流,会开启低延迟模式,所谓低延迟模式就是会极大的减少帧缓存, 基本上视频帧立马解码立马播放减少每个分片的起播延迟。但是呢在 CPU 负载过高的情况下(解不过来)会造成视频频繁卡顿(网络无关的)。

2.不同浏览器表现有差异

1)timeupdate 事件。W3C 的标准是不能超过 250ms 触发一次。windows 下 360 等浏览器会 达到 500ms 左右。
2)safari 对每一帧 duration 平顺度更敏感。safari 需要对每一视频帧的 duration 标准化处理, 例如 TS 下要处理成 3600。
3)对洞的容忍度不同。chrome 遇到 buffer 中有 0.08 的间隔以内会自动跳过去。像 IE edge 等浏览器不行会卡住,所以播放器一定要有跳洞逻辑。比如判断当前卡在洞的边界,要主动跳 过去(seek)。

3. 内存限制

通过 MSE push 给 video 的视频数据会在内部维护一个 buffer,这个尺寸是有限制的。
1)chrome 系列约 100M 2)IE 系列约 30M
超过的话就会导致抛出 QuotaExceededError。所以需要处理好 buffer 的尺寸以及及时清除 不用的 buffer。比如已经播放过的,正常浏览器会自己清除,但是不那么的及时。

五、优化

简单说一下卡顿相关的优化。
 多级 Buffer 控制
 ABR 自适应码率算法
 基于 WebRTC 的 P2P

1.多级 buffer

为什么要有多级的 buffer?因为 video 本身的解码 buffer 有大小限制,而且 buffer 过长会导 致长时间解码,会导致 CPU 一直占用高。所以我们搞了两级 buffer 一级就是 video 的 buffer 另 外一级是内存中的,只负责下载,二级很长。可以消除网络抖动带来的卡顿影响。

2.ABR 自适应码率的算法

这个主要是来预测用户本身的带宽范围,然后选用不同码率的视频流来无缝切换播放。当 然还有一些策略算法,比如根据用户现在 buffer 的水位,或者检测到用户频繁超时,来采用不 同的策略。

3.基于 WebRTC 的 P2P

因为 P2P 是基于 UDP 的传输,可以突破一些带宽限制或网络拥塞而导致的卡顿问题。不 过 P2P 不一定靠谱所以还是要辅以普通的 HTTP 传输相结合。我们一般是利用 P2P 加 indexDB 来变相延长视频的缓冲区。因为 P2P 带宽成本便宜,我们利用 P2P 做了一个非常长又很便宜的 buffer。这样的话网络再波动也不会导致卡顿了。

上一篇:视频用户网络画像与应用


下一篇:数据分析实际案例之:pandas在餐厅评分数据中的使用