突然发现这篇文章躺在草稿箱里几年时间,在直播最火的时候帮了大忙…
希望还能够提供帮助…
前言
flv/rtmp是当下最流行的视频传输协议,这也和flash的没落有很大的关系,众浏览器已经默认禁止flash,而在使用flv.js过程中根据项目实际需求的不同,对直播视频的延迟要求均不同,正常flv.js的延迟在5s左右。那么如何降低呢?
常见的直播协议
-
RTMP:底层基于TCP,在浏览器端依赖Flash。
-
HTTP-FLV:基于HTTP流式IO传输FLV,依赖浏览器支持播放FLV。
-
HLS: Http Live Streaming,苹果提出基于HTTP的流媒体传输协议。HTML5可以直接打开播放。
-
WebSocket-FLV:基于WebSocket传输FLV,依赖浏览器支持播放FLV。WebSocket建立在HTTP之上,建立WebSocket连接前还要先建立HTTP连接。
而就官方给出的延迟排序,HLS排名最后,其他延迟均相等。
Flv.js 限制
- FLV里所包含的视频编码必须是H.264,音频编码必须是AAC或MP3, IE11和Edge浏览器不支持MP3音频编码,所以FLV里采用的编码最好是H.264+AAC,这个让音视频服务兼容不是问题。
- 对于录播,依赖 原生HTML5 Video标签 和 Media Source Extensions API
- 由于依赖Media Source Extensions,目前所有iOS和Android4.4.4以下里的浏览器都不支持,也就是说目前对于移动端flv.js基本是不能用的。(媒体源扩展(MSE)实现后,情况就不一样了。MSE 使我们可以把通常的单个媒体文件的 src值替换成引用 MediaSource 对象(一个包含即将播放的媒体文件的准备状态等信息的容器),以及引用多个 SourceBuffer 对象(代表多个组成整个串流的不同媒体块)的元素。MSE 让我们能够根据内容获取的大小和频率,或是内存占用详情(例如什么时候缓存被回收),进行更加精准地控制。 它是基于它可扩展的 API 建立自适应比特率流客户端(例如DASH 或 HLS 的客户端)的基础)
Flv.js 延迟优化
在进行延迟优化时我们需要了解Flv.js的工作流程:(内容来自网络)
-
主播端在采集到一段时间的音视频原数据后,因为音视频原数据庞大需要先压缩数据:
-
通过H264视频编码压缩数据数据
-
通过PCM音频编码压缩音频AAC数据
-
-
压缩完后再通过FLV容器格式封装压缩后的数据,封装成一个FLV TAG
-
再把FLV TAG通过RTMP协议推流到音视频服务器,音视频服务器再从RTMP协议里解析出FLV TAG。
-
音视频服务器再通过HTTP协议通过和浏览器建立的长链接流式把FLV TAG传给浏览器。
-
flv.js 获取FLV TAG后解析出压缩后的音视频数据喂给Video播放。nginx-http-flv-moudle
流程后我们就知道从哪入手优化了,以下解决方案收集自网络 划重点
-
主播端采集时收集了一段时间的音视频原数据,它专业的叫法是GOP。缩短这个收集时间(也就是减少GOP长度)可以优化延迟,但这样做的坏处是导致视频压缩率不高,传输效率低。
-
关闭音视频服务器的I桢缓存可以优化延迟,坏处是用户看到直播首屏的时间变大。
-
减少音视频服务器的buffer可以优化延迟,坏处是音视频服务器处理效率降低。
-
减少浏览器端flv.js的buffer可以优化延迟,坏处是浏览器端处理效率降低。
-
浏览器端开启flv.js的Worker,多进程运行flv.js提升解析速度可以优化延迟,这样做的flv.js配置代码是:
{
enableWorker: true,
enableStashBuffer: false,
stashInitialSize: 128,
}
但是问题来了,根据网上各种解决延迟的方案我们会发现,延迟仍在5s左右!并伴随着卡顿,丢帧。
通过查询MDN,
媒体元素支持在媒体的内容中从当前播放位置移到某个特定点。 这是通过设置元素的属性currentTime的值来达成的
也就是说我们可以通过更改播放器的currentTime来修改当前播放内容,那么具体更改到哪里呢。我可以从缓存中读取,因为播放器在处理直播流时,他会预先缓存你的视频内容,比如,当前视频播放之12:00,但因为延迟,播放器只播放到11:58,但其实当前直播流已经将12:00的视频进行缓存,我们直接从中读取即可。
// 当前播放器 dom 节点
flvPlayer.buffered.end(0)
buffered 属性返回 TimeRanges 对象。
TimeRanges 对象属性:
length – 获得音视频中已缓冲范围的数量
start(index) – 获得某个已缓冲范围的开始位置
end(index) – 获得某个已缓冲范围的结束位置
注释:首个缓冲范围的下表是 0。
这样我们就拿到了当前已缓存的最新帧。也就可以计算出当前直播流的延迟时间
let delayTime = flvPlayer.buffered.end(0) - flvPlayer.currentTime
只需判断如果延迟 > 2s ,强行将直播流拽回当前缓存的最新帧即可
if ((flvPlayer.buffered.end(0) - flvPlayer.currentTime) > 2) {
videoElement.currentTime = videoElement.buffered.end(0.1)
}
细心的同学发现了,buffered.end(index),index给的值是0.1,index的范围时[0, 1],为什么不是0?因为如果将index的值设置为0,当前直播流会变成0缓存解码加载,在网络较差的环境下会导致卡顿,丢帧等问题。
解决方案来自flv.js作者xqq
所以可以优化为 全部代码入下:
setInterval(function () {
if (videoElement.buffered.length > 0) {
var delayTime = flvPlayer.buffered.end(0) - flvPlayer.currentTime
if ((flvPlayer.buffered.end(0) - flvPlayer.currentTime) > 2) {
videoElement.currentTime = videoElement.buffered.end(0.1)
}
}
}, 1000)
每秒去计算延迟,然后解决延迟。可以有效的将代码控制在2s以内。
其实还有一些小问题!
也就是buffered可能不存在!我们只需在处理延迟之前先去判断当前是否有缓存!
buffered.length > 0
其他
其实前端能对延迟做的优化很少,并不是每个人都能去写出flv.js或者去修改它的源码!那么直播流的质量就额外的重要!
以下几点均可能导致视频延迟:
- 视频帧率及清晰度
- 推流工具
- 视/音频质量 根据情况进行压缩
- 推流服务器
- 网络
- 摄像头参数