编者按:视频协作渠道会涉及网络、编解码等众多技能栈,而且要支撑各类终端。其中一个关键能力是完成毫秒级的同步,这对于视频协作渠道十分重要。本文来自分秒帧 web多媒体开发工程师耿学岩的投稿,详解了完成毫秒级同步遇到的两个挑战和处理方案。最终,假如你有必定的经验和考虑又乐于共享,欢迎经过 editors@livevideostack.com 投稿给咱们。

文/耿学岩

背景

分秒帧是一个音视频生产协作渠道,其中用户能够经过在视频的某个时刻点提出定见或共享来交流对视频的修改定见。由于客户有时需求对时刻精确到帧进行定位,咱们需求保证不同转码视频在播映时,时刻定位能够精确到毫秒等级。在满足这一要求的同时,咱们还必须考虑不同网络条件、不同端和不同使用场景。咱们在处理这些问题的过程中发现了一些问题,本文将对这些问题进行评论。

为什么需求画面帧的精确性?

在线视频协同:探究画面帧的准确性

图:用户发送的批注

在线视频协同:探究画面帧的准确性

图:审理者看到的

当用户发送批注需求审理者根据批注定见做出修改时,假如没有画面校准,此刻审理者一脸黑人问号, 哪来的“T” ? 然后再私下交流吗?信息存在差错, 审理批注就毫无意义。

问题一:保证浏览器中 Video 标签时刻定位在 pause 时的精确性

当用户在播映视频时暂停,并对视频进行批注,然后持续播映时,有时会发现定位回原始批注时刻点时画面会有一帧的误差。这是由于,咱们在暂停时记录了视频的当时时刻(即 currentTime)并经过 seek() 办法回到该时刻点,但是这个办法并不能保证回到的画面完全精确。

现象

暂停批注时 没有矫正currentTime,当批注发送成功后,主动跳回批注点,画面产生了变化,以下是用户所不想看到的画面:

JS代码如下:

JavaScript
var videoDom =  document.getElementsByTagName('video')[0]
videoDom.pause()
var currenttime = videoDom.currentTime
videoDom.currentTime = currentTime // 此刻画面有概率产生改动

问题产生原因

咱们在处理这个问题时发现,这个问题是由 JavaScript 履行机制导致的。在浏览器中,JavaScript 是单线程履行的。当咱们调用 pause 办法时,实践上是将该操作增加到了事情队列中。当事情轮询到这个暂停操作时,才会真正履行 pause 办法。而在这个过程中,获取 currentTime 的操作已经完成了。这就导致了两个操作之间的时刻差。假如这个时刻差刚好产生在视频帧切换的时分,就会导致画面误差一帧。

举个比如,假如一个视频有 25 帧,那么第 0-40ms 是榜首帧画面,第 41-80ms是第二帧画面,以此类推。

在线视频协同:探究画面帧的准确性

当用户在播映榜首帧画面时按下暂停按钮,咱们认为JavaScript 会立即履行逻辑并通知 Video 标签中止播映,但实践上暂停操作会被参加事情队列中等待履行。假如暂停操作前面还有其他事情正在排队,等履行到暂停操作时就会有必定的时刻差。假如这个时刻差刚好产生在第 41 ms,画面会跳到下一帧画面。但是,咱们拿到的currentTime仍是榜首帧画面的。

处理方案

为了保证在暂停时和检查批注时 currentTime 的共同性,咱们在暂停时对 currentTime 进行了矫正。这样,当用户暂停时进行批注,然后再设置 currentTime检查批注时,就不会出现画面误差问题。经过这种方法,咱们就能保证画面在暂停时和检查批注时的精确性。

问题二:HLS流中视频 duration 值变化反常

在咱们的使用中,咱们需求保证各端的视频总时长和总帧数共同。为了完成这个目的,咱们一般会在浏览器 Video 标签的 durationchange 事情触发时获取视频总时长,并经过帧率核算出总帧数。durationchange 事情是当视频总时长产生改动时触发的。当视频加载前,总时长为默认值”NaN”,当视频加载完成后,durationchange 事情触发,总时长会变成视频的实践总时长。

在加载和播映视频时,浏览器会用Video标签来追寻视频的状况。共有五个状况,分别是:[1]。

在线视频协同:探究画面帧的准确性

在视频加载和播映过程中,浏览器Video标签的 readyState 会产生变化。在这个过程中,MP4文件和HLS文件的 duration 改变机遇是不同的。

MP4

在 MP4 文件的加载过程中,durationchange 事情会在资源开始加载(loadstart)之后,在元数据已加载(loadedmetadata)之前触发。此刻,浏览器会解析 MP4 文件中的 moov box,并获取视频时长。因此,在 durationchange 事情触发时,能够获取到较为精确的 duration 。

HLS

咱们发现在加载 HLS 流时,浏览器 video 标签的 duration 会产生多次改变。

榜首次改变在loadstart之后 loadedmetadata 之前 而且 readyState === 0 时调用,此刻已拿到相对精确的 duration,≈ ffmpeg取到的 durantion。

举个比如,ffmpeg截图如下:

在线视频协同:探究画面帧的准确性

第二次改变在loadstart之后 loadedmetadata 之前 而且 readyState === 1 时调用,此刻拿到的时长由 m3u8 文件解析得到。

第三次改变在加载到最终一片 ts 时调用。咱们发现这三次改变的时长并不共同。因此咱们需求在这三次改变中取一个更精确的时长作为视频时长。

举个比如,三次时长比较:

在线视频协同:探究画面帧的准确性

HLS三次取值时长不共同的原因

榜首次:在loadstart后loadedmetadata前readyState === 0时调用,视频的实践时长已被解析出来,机遇和机制类似于MP4文件(榜首次调用时就能够获取到duration的值)。

第二次:在loadstart后loadedmetadata前readyState === 1时调用,hls.js解析完m3u8索引文件并经过#EXTINF核算出视频的实践时长。

举个比如,以下是一个m3u8文件信息:

在线视频协同:探究画面帧的准确性

第三次:当加载完最终一片ts 此刻一切音频和视频帧信息已经能够全部拿到。

举个比如,以下是帧信息:

在线视频协同:探究画面帧的准确性

在线视频协同:探究画面帧的准确性

在线视频协同:探究画面帧的准确性

在线视频协同:探究画面帧的准确性

best_effort_timestamp_time :媒体流中的一个标识符,用于标识每一帧的时刻戳。

pkt_duration_time :媒体流中的一个标识符,用于标识每一帧的持续时刻。

一般,best_effort_timestamp_time 和 pkt_duration_time 会用在音视频同步、流量控制、缓存等方面。[2]

尾音频/视频信息中的 best_effort_timestamp_time 和 pkt_duration_time 可用来核算音频/视频的完毕时长。在这个案例中,音频完毕时长由 best_effort_timestamp_time 和 pkt_duration_time 相加所得(即 96.230300 + 0.023211 = 96.253511),视频完毕时长也是如此(即 96.229778 + 0.016667 = 96.246445)。

咱们发现,音频完毕时长 – 音频首个best_effort_timestamp_time约等于第三次获取的duration。具体来说,音频的完毕时刻比视频的完毕时刻长,同时音频的榜首个时刻戳早于视频的榜首个时刻戳。为了包括最完好的时刻长度,需求将音频和视频时刻戳中的最小值和最大值来进行核算。这种状况或许出现在音频和视频的录制或处理过程中,需求进行相应的调整以保证两者之间的同步和共同性。

参考资料

1.developer.mozilla.org/zh-CN/docs/…

2. www.jianshu.com/p/c9b772233…