直播观看端音视频与文档翻页的同步问题

同步问题描述

演讲教育类直播一般都会有相应的文档,ppt展示,这种直播文档呈现方式一般有两种

  1. 通过抓取桌面,共享应用窗口等方式将文档展示编码到视频流里呈现,主讲人可以以画中画的方式混合入视频流中,obs就可以做到
  2. 视频流直播只有主讲人画面,文档通过其它的方式单独程序,如将文档转为h5动画效果呈现,或者在移动app中单独的文档区域呈现,这种一般都是直播平台私有的呈现方式

这两种呈现方式,第一种是一路直播流中呈现,没有文档和主讲人音视频的同步问题,但是文档清晰度受直播流编码参数的影响。 第二种则相对灵活,事先转好码的文档能清晰地呈现,并且观看端对文档可以灵活控制,如自由翻页,自由查看文档信息等,但是存在音视频流和文档翻页的同步问题。

同步问题分析

音视频流经过采集编码,通过传输协议推送到服务端,服务端经过分发到达观看端,这里存在一定的延时,延时受多方面因素的影响,编解码延时,网络带宽和jitter,流媒体协议和分发网络的中转,播放器端缓存控制等。

所以直播过程中音视频流的延时很难避免,现在的互联网直播一般是使用rtmp协议推流,通过云平台做分发,协议转换,在观看端使用rtmp,http-flv, hls等流媒体协议观看直播,其中rtmp,http-flv实际使用一般有3-5s的直播延时,hls在移动端使用较多,尤其是ios移动h5端,hls几乎是唯一的播放协议,hls因为协议原因和播放端的缓存策略,延时多达十几秒甚至几十秒,并且延时不太稳定。

文档单独呈现一般是事先转码完毕,主讲人翻页信息发到后台,再转到观看端的应用程序,就可以立刻获取到翻页信息了,这里的延时也就是几个http请求的延时。

所以直播中观看端音视频流和文档翻页的同步问题就出现了。

一些解决方案

可以看到解决同步问题需要文档的翻页动作和音视频流之间有个基准,比如他们共同使用同一基准的时间戳,在观看端只要能解析到这个时间戳,那么就能做到同步了。

同步问题需要多端的配合,从推流接入端和文档翻页的源端就要有所作为,比如推流端,可以在推流端给码流打上一些可关联实际时间的时间戳,或是在码流中插入一些信息。文档翻页端需要传递翻页信息,翻页时间点给后台。 播放端要能解析获取这些信息,能做到这些,同步问题也就迎刃而解了。

但是直播系统,要考虑扩展性,一般流媒体接入端和观看端,都会有第三方通过标准协议支持。这样很多时候就无法在推流源端去做一些特殊操作了,这时候只能退而求其次,在服务端记录码流的时戳信息。 这里面有关同步的误差就出现了,就是推流端到服务端的流延时,好在这一段延时比较短,在推流到观看的整个链路上这段延时的影响不大。

直播系统中服务端可以处理码流,加上同步信息,在观看端需要读取这些信息,做出同步操作。

native端

对于native端的播放器来说,实现同步是水到渠成的,码流里打入了什么信息,只要读出来就行了。

h5端

对于web h5的观看端,由于播放是依赖于浏览器的,所以码流中的信息是不一定都能取得的,h5的播放只能依靠video标签中一些有限的信息.现在流行的h5播放库有flv.js,hls.js,video.js等等.

这些js类库原理是类似的,都是播放依赖于http协议的流媒体,处理bitstream码流,使用javascript将bitstream转封装为mse支持的fragment mp4,利用浏览器的mse接口播放.

这个过程可以完全控制码流的获取和对封装格式的解析,以及码流的播放速度和buffer控制.

有了以上的控制能力和码流信息,配合服务端也就能相应控制音视频和文档同步了.

ios端

但是ios的浏览器是不支持mse的,ios浏览器原生支持hls协议,也就是可以用video标签直接播放m3u8码流,毕竟hls是苹果公司提出的协议.

没有了mse,音视频和文档同步的问题怎么办呢?

回到之前的思路,要做同步,必须要从播放端获取码流当前的播放信息,那只能从video标签中取得信息了.

video标签的currentTime属性是相对于开始时间的当前播放的时间,但是直播中无法定位播放起始位置,也就无法定位播放位置,没办法做同步.

再看video标签的其他属性和方法,有一个Date getStartDate()方法,这是https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video的一段描述

1
2
3
4
5
6
If the media is being streamed, it's possible that the user agent may not be able to 
obtain some parts of the resource if that data has expired from the media buffer. 
Other media may have a media timeline that doesn't start at 0 seconds, 
so setting currentTime to a time before that would fail. 
The getStartDate() method can be used to determine 
the beginning point of the media timeline's reference frame.

也就是getStartDate()可以获取起始播放位置,那么配合currentTime就可以定位当前播放码流的位置了.

看起来问题就要解决了,继续思考getStartDate()获取的值是什么呢,可以写一个html页面来实测一下,实测下来播放一个直播hls这个接口获取的是NaN,并没有返回任何有效数据.

再细细思考一下,这个值是表示播放端播放码流的起始位置,那么这个起始位置点时间信息在哪里呢,如果码流本身没有这个时间信息,那么接口当然获取不到.

这么一想,再结合hls协议来看,m3u8文件见的多了,也就自然能想到这样一个标签#EXT-X-PROGRAM-DATE-TIME.看看hls协议文档中对这个标签的描述

1
2
3
4
5
6
7
8
The server MAY associate an absolute date and time with a Media
Segment by applying an EXT-X-PROGRAM-DATE-TIME tag to it.  This
defines an informative mapping of the (wall-clock) date and time
specified by the tag to the first media timestamp in the segment,
which may be used as a basis for seeking, for display, or for other
purposes.  If a server provides this mapping, it SHOULD apply an EXT-
X-PROGRAM-DATE-TIME tag to every segment that has an EXT-X-DISCONTINUITY 
tag applied to it.

这个标签就是媒体片段的起始绝对时间,hls协议设计它就是为了给应用程序提供一个参考,做一些跳转,显示和其他的一些目的.

把这个标签加到hls码流中,再来看看效果,html代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!doctype html>
<html>
   <head>
      <title>Video Player</title>
   </head>
   <body>
	   <div>
      <video id="myVideo" width="640" height="480" controls autoplay >
             <source src="test.m3u8">
      </video>
	  <p id="demo"></p>
	  <button onclick="myFunction()">clickclick</button>
	  </div>
	  <script>
	  function myFunction() {
	  	var x = document.getElementById("myVideo").getStartDate().getTime();
	  	var y = document.getElementById("myVideo").currentTime;
	  	document.getElementById("demo").innerHTML = x + "    " + y;
	  }
	
	  </script>

  </body>
</html>

getStartDate()获取了ios浏览器第一次获取的m3u8文件中的第一个#EXT-X-PROGRAM-DATE-TIME的值,currentTime获取了当前播放的位置相对于getStartDate()的偏移

有了这些信息,就能精准的定位当前播放器播放的位置了,也就能与外部的文档同步了.

这是其中一个ios h5的hls播放同步文档问题的解决方案,仅供参考.