1. 环境搭建
成功的一个参考 参考 参考
2. 低延时的参考资料
《让子弹飞》剧照
借《让子弹飞》中姜文的名言作为开场白:让子弹飞一会儿。
某名人吐槽说:还要飞一会儿哪?这子弹的延迟也忒大了。
该名人就是鄙人。为什么低延迟很重要?
低延迟的子弹可以击杀敌军千米之外,低延迟的直播技术可以秒杀粉丝千里之外。
互动直播技术已经成为直播平台的标配。没有互动直播技术的直播平台无法跻身直播行业第一梯队。而要获得互动直播技术,实现低延迟是必须的。
因此,低延迟很重要。
那么,直播技术如何实现低延迟呢?
综合目前业内领先的互动直播技术的经验,和各位分享一下如何实现低延迟。
目前行业领先的互动直播技术,连麦方的延迟400毫秒,观看方的延迟1秒左右。目前国内的主流直播平台都采用连麦互动直播技术。因此,这个直播技术经验是经过市场验证的,是从实操中得来的,而不是单凭理论分析得到的。
一般来说,延迟低于800毫秒, 才能够在直播中连麦,做一些比较高频的互动,比如相声或者谈话节目。如果延迟高于800毫秒,在直播中连麦的效果就无法被观众接受了。因此,延迟400毫秒的直播技术,是有足够的余地去实现连麦互动直播业务的。
要在直播技术中实现低延迟,有一个简单而要务实的哲学:
1)选择一条最优的路径;
2)在这条路径上做到最优;
3)保持所有路径优质。
下面我将按照这个思路来论述如何实现低延迟。
选择一条最优的路径
要选择一条最优的路径,有很多方法。目前使用比较多的是网络测速,用户个人连接数据分析,和用户群体连接数据分析等几种方法来选择最优的网络路径。
网络测速
推流端在推流之前,向各个路径发送简单的数据包,然后根据数据包响应的时间来推测哪条路径最快。这个方法比较简单,有效然而有限:选出来的路径只是在该测试时间点最快的,而网络状况是随着时间变化的;另外,简单数据包测出来速度比较快,并不代表流媒体传输数据速度也比较快。因此,这个方法得到的结果只能作为一个指标来参考。
大数据分析
为了回避单个采样时间点测速导致的偏差,可以采取对历史大数据进行分析,预测哪个网络路径最优。对历史大数据进行的分析分为两个维度:用户个人连接数据分析和用户群体连接数据分析
1) 用户个人连接数据分析
每个主播用户的使用历史数据是有规律可循的。通过分析这些历史数据,可以发现主播用户从哪里接入,在什么时候接入,接入到哪个服务器,走什么路径的效果最优。这些历史数据积累得越丰富,历史数据分析得出来的结论就越靠谱。这个方法能够发现个人主播用户周期性的网络连接情况,能找出大部分时间连接效率最优的网络路径。然而,这个方法的缺点是:数据采样只是基于单个用户,采样点太少,没有全局考虑到该用户所在地区的整体网络连接情况。
2) 用户群体连接数据分析
为了弥补用户个人连接数据分析的不足,这里引入另外一个维度的数据分析:某地区用户群体连接数据的分析。针对某用户所在区域的用户群进行历史数据分析,可以发现这个区域网络连接随着时间变化的规律,找出在不同的时间点,在不同的接入点连接到哪个服务器最好。
单点网络测速,用户个人连接数据分析,再加上用户群体连接数据分析综合得到结论,就能比较有效地预测哪条路径最优。选路这部分需要不断地优化,才能积累丰富的用户数据,同时适应网络的变化。
在这条路径上做到最优
选好最优的路径以后,剩下的就是要在该路径上做到最优。这条路径包括了下面几个环节:采集,编码,推流,转码,分发,拉流,解码和渲染。在一个实时的音视频系统架构里,每个环节都会有一定程度的优化空间。行业内的小伙伴在这条路上已经有过很多探索,这里不想重复讨论别人已经探索过的议题,而只重点讨论下面几个关键点。
选择协议
传输协议的选择十分重要。传输协议一定程度上就决定了延迟的范围。选择传输协议的时候要考虑是推流端还是拉流端。推流端的协议有RTMP, WebRTC和基于UDP的私有协议。
1) RTMP是基于TCP的标准协议,CDN网络普遍支持,也能做到相对比较低的延迟。即构科技的互动直播技术在推流端使用RTMP协议,拉流端兼容三种协议:RTMP,HLS和FLV。HLS协议的延迟比较大,在需要进行连麦互动的场景下,不应该使用HLS协议。
2) WebRTC的好处在于用户体验好,不需要安装东西,分享一个链接就可以看。但是它有一个缺点,就是WebRTC是Google推的一项技术,除了Google Chrome和Opera支持WebRTC,其他浏览器大部分不支持WebRTC。换一句话说,40%的浏览器支持WebRTC,剩下60%浏览器不支持,所以适用范围就比较局限。然后,在中国国内,WebRTC在Google Chrome上的表现也大打折扣。最后,因为浏览器没有开放核心的能力,所以在浏览器上运行的协议比较难以做到比较低的延迟。
3) 基于UDP的私有协议十分适合做实时音视频系统,它是面向无连接的,避免了TCP做网络质量控制所需要的开销,能够做到比较低的延迟。但是它也有一个缺点,那就是私有协议的兼容性不好。CDN支持标准的RTMP协议,但是不支持基于UDP的私有协议。为了吸纳UDP的优点,而避免UDP的缺点,即构科技的互动直播技术采用了基于UDP的私有协议作为补充,在有必要的时候用来弥补RTMP协议的不足。比如说,只有在网络环境比较恶劣或者在跨国互通的情况下,才使用基于UDP的私有协议;比如说,只在推流端到媒体服务器这一段才使用基于UDP的私有协议,而从媒体服务器转推流到CDN网络这一段采用RTMP协议,在这两段之间通过把UDP私有协议转换成RTMP协议来进行适配和衔接。这样一来,即构科技的直播方案既拥有超低延迟的优势,又保留了标准协议普遍被CDN网络支持的好处。
前向纠错和丢包重传
前向纠错简称FEC,英文全称Forward Error Correction,是通过提前采取措施来对抗网络损伤。丢包重传主要针对丢包的情况下,有针对性地对丢失的数据包进行高效率的重传。准确来说,它们的直接目的不是为了降低延迟,而是为了对抗网络损伤。在不可预测的网络环境中,能很好地处理网络抖动带来的负面影响,间接也会降低了延迟,同时保证了稳定性和流畅性。一般来说,前向纠错和丢包重传互补使用,前者属于前验的方法,比较节省时间,但是占用多余的带宽;后者属于后验的方法,比较节省带宽,但是会消耗比较多的时间。在网络比较差情况下,丢包率比较高,那么可以通过前向纠错方法来保证信息完整送达。比如说发送冗余信息,确保在一定丢包率之下,接受方也能准确而完整的还原发送方所要发送的信息。在网络相对比较好的情况下,丢包率比较低,那么可以通过丢包重传的方法来保证信息完整送达。比如说针对丢掉的数据包,通过高效的机制进行重传,确保接受方能够完整的收到发送方所要发送的消息。
缓冲自适应
由于有网络抖动的存在,数据包的到达不是匀速的。最直接的降低延迟的方法就是把缓冲队列的长度设置为零,接收到什么数据包就直接渲染什么数据包,然而这样做的后果就是播放不流畅,会出现卡顿。因此,延迟和流畅两者本身就是一对矛盾的因素。我们要做的是寻找低延迟和流畅之间的平衡点,寻找平衡点的有效方法就是建立缓冲队列。在拉流端和混流服务器都需要建立缓冲队列。对于一个实时系统来说,缓冲队列的长度必须不是固定的,而是自适应的:当网络很好的时候,缓冲队列的长度就会变得比较短,接近零,甚至为零;当网络不好的情况下,缓冲队列的长度会变得比较长,但是不能超过能接受的上限,毕竟缓冲队列的长度本质上就是延迟的时间。另外,还可以把缓冲自适应技术和快播或慢播技术结合起来使用。当网络由差转好的情况下,可以适当的播得快一点,尽快缩短缓冲队列的长度。当网络由好转差的情况下,可以适当的播得慢一点,让缓冲队列适当变长,保持流畅性。快播和慢播是结合观众的心理学模型,在适合快播和慢播的条件下采用,让观众没有觉察出播放速度的变化,同时整体感觉也显得既流畅又低延迟。
码率自适应
由于网络环境的复杂多变,码率要能自动适应网络状况的变化,也就是所谓的码率自适应。 在网络比较差的时候,要降低码率,让直播保持低延迟和流畅性;在网络比较好的时候,要提高码率,让直播保持高清画质。为了做到码率自适应,对协议选择也很考究。RTMP对码率自适应能做的事情比较有限,因为它基于TCP, 而TCP 下层已经做了网络质量控制,当网络出现拥塞的时候,上层应用不会及时得到通知。基于UDP的私有协议更加适合做码率自适应,因为它基于UDP,而UDP只负责发包和收包,把网络质量控制交给应用层来做,这样应用层会有足够的空间来实现码率自适应。
保持所有路径优质
那么,为了在直播技术中实现低延迟,要选择一条最优路径,还要在该路径上做到最优。故事讲完了吗?没有,我们忘记了一个前提:整体的道路网络必须要足够好。道路网络不好,怎么选都是烂泥土路,选了烂泥土路,如何能够跑的快呢?因此,要实现低延迟,网络基建必须要足够好。网络基建的质量可以通过以下三个方面来提高:
1) 全网充分覆盖:一般来说,音视频云服务的机房会分布在核心的几个枢纽城市,边远地区的用户的访问质量是得不到保障的。另外,在中国国内,各个网络运营商的覆盖面是参错不齐的,有些网络运营商对一些边远地区也是覆盖不足的。为了做到全网充分覆盖,可以采用多节点代理和重定向,来确保全网充分覆盖无盲点。这个需要经过实际充分测试,才能够验证各类网络可以充分连通。
2) 全方位保障QoE:网络接入点的覆盖面对QoE(Quality of Experience)十分的重要。从即构科技的经验来看,通过部署遍布全球范围的接入点能够确保这一点。另外,由于在中国国内存在有“两张大网,多张小网”这样一个局面,BGP在这种情况下十分有必要。BGP能够很好地解决不同网络之间的互通问题。即构科技所有的网络接入点都使用了BGP。
3) 优质的网络节点资源:音视频云服务是跑在网络基建上面的,下层网络基建的质量必须要优质,而且音视频云服务和下层网络基建也要深度结合。为了实现直播技术的低延迟,最好能对接一线的网络运营商,这样部署的网络节点资源无论是数量还是质量上都是有充分的保障。这也是即构团队在与映客直播,花椒直播,一直播和栗子直播等一线直播平台合作的过程中,在腾讯QQ过去十多年海量用户运营的过程中总结出来的经验。
综合来说,要实现直播技术低延迟,必须要选好一条最优的路径,然后在该路径上做到最优,最后要确保所有路径的质量都是好的。道理就是那么简单,实现起来就是那么难,魔鬼都出在细节上。
View Code
4. 安卓直播
ffmpeg推流命令
这个是从服务器端从视频文件转换为视频流进行推送,此处使用obs,暂未用到这个。
ffmpeg -re -i Rec.mp4 -vcodec libx264 -acodec aac -f flv -c copy -flvflags no_duration_filesize rtmp://localhost:1935/myapp
文件配置
cp -f /usr/local/nginx/modules/test/nginx.conf /usr/local/nginx/conf
worker_processes 1;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
application myapp {
live on;
hls on;
hls_path /usr/local/nginx/myapp;
#record keyframes;
#record_path /tmp;
#record_max_size 128K;
#record_interval 30s;
#record_suffix .this.is.flv;
#on_publish http://localhost:8080/publish;
#on_play http://localhost:8080/play;
#on_record_done http://localhost:8080/record_done;
}
}
}
http {
server {
listen 8080;
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root /usr/local/nginx/modules/nginx-rtmp-module/;
}
location /control {
rtmp_control all;
}
#location /publish {
# return 201;
#}
#location /play {
# return 202;
#}
#location /record_done {
# return 203;
#}
location /rtmp-publisher {
root /usr/local/nginx/modules/nginx-rtmp-module/test;
}
location / {
root /usr/local/nginx/modules/nginx-rtmp-module/test/www;
}
}
}
View Code
核心要点
建立nginx安装目录:/usr/local/nginx,安装目录下建立myapp、modules。modules下面放rtmp的模块。在其它地方配置nginx源码并指定rtmp,编译并安装即可。
就是rtmp模块要位于nginx安装目录下面。最后,所有的编译安装配置启动等都是用的root用户。
OBS推流设置
查看推流状态
使用http的端口号(相当于80端口号):
http://192.168.186.1:8080/stat
网页端浏览
只有IE的浏览成功了。hls只支持苹果系列。
接下来研究这种格式的需要什么样的js去解析。
几个配置文件的设置
①
vim /usr/local/nginx/modules/nginx-rtmp-module/test/rtmp-publisher/player.html
<!DOCTYPE html>
<html>
<head>
<title>RTMP Player</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
var flashVars = {
streamer: 'rtmp://192.168.186.1:1935/myapp',
file:'mystream'
};
var params = {};
params.allowfullscreen = "true";
var attributes = {};
swfobject.embedSWF("RtmpPlayer.swf", "rtmp-publisher", "640", "480", "9.0.0", null, flashVars, params, attributes);
</script>
</head>
<body>
<div id="rtmp-publisher">
<p>Flash not installed</p>
</div>
</body>
</html>
View Code
②
vim /usr/local/nginx/modules/nginx-rtmp-module/test/www/record.html
<a href="index.html">Play</a> | <b>Record</b>
<br/>
<script src="jwplayer_old/swfobject.js"></script>
<script type="text/javascript">
var flashvars =
{
'streamer': 'rtmp://192.168.186.1:1935/myapp',
'file': 'mystream',
'type': 'camera',
'controlbar': 'bottom',
'stretching': 'none',
'frontcolor': '86C29D', // text & icons (green)
'backcolor': '849BC1', // playlist background (blue)
'lightcolor': 'C286BA', // selected text/track highlight (pink)
'screencolor': 'FFFFFF', // screen background (black)
'id': 'playerID',
'autostart': 'true'
};
var params =
{
'allowfullscreen': 'true',
'allowscriptaccess': 'always',
'bgcolor': '#FFFFFF'
};
var attributes =
{
'id': 'playerID',
'name': 'playerID'
};
swfobject.embedSWF('jwplayer_old/player.swf', 'player', '320', '260', '9.0.124', false, flashvars, params, attributes);
</script>
</head>
<body>
<div id="playercontainer" class="playercontainer"><a id="player" class="player" href="http://get.adobe.com/flashplayer/">Get the Adobe Flash Player to see thi
s video.</a></div>
</body>
</html>
View Code
③
vim /usr/local/nginx/modules/nginx-rtmp-module/test/www/index.html
<b>Play</b> | <a href="record.html">Record</a>
<br/>
<script type="text/javascript" src="/jwplayer/jwplayer.js"></script>
<div id="container">Loading the player ...</div>
<script type="text/javascript">
jwplayer("container").setup({
sources: [
{
file: "rtmp://192.168.186.1:1935/myapp?carg=1/mystream?sarg=2"
}
],
image: "bg.jpg",
autostart: false,
width: 640,
height: 480,
primary: "flash"
});
</script>
View Code