NVR直播疑难解决

前言

■ 需求

提供小程序播放(安卓和ios实现不同)、pc播放(各种业务的单路播放和大屏页面的9路并行播放并轮询)

■ 实现

走的国标gb28181,rtsp取流,pc和安卓采用ws-flv,前端连接为ws连接,ios采用hls,前端请求为m3u8列表,ts切片流。

一一解决

■ 项目情况

合同的开发早已完成,但客户对播放效果非常不满,导致项目迟迟无法验收。由于项目未结束,客户也会不停的提出其它的业务迭代要求,我方不断的进行无利润的更新迭代。由于视频播放差,开发周期不断延长,本是香饽饽的项目也变成了一个累赘合同。

■ 1.初始状况

客户反馈:
大屏完全播不了,就算能播也是卡顿、花屏,效果非常差。小程序安卓能播,ios看不了?
复现:
经理是着重测试第二点,提出的是安卓很正常,让大伙看看ios播放效果。测试无法出结论,同样的条件下有时正常播放,有时完全不能播放,上一分钟我能播放,下一分钟另一个人点就不能播放,完全摸不出规律。而且当时我使用的是用了6年的iPhone6,wifi坏的差不多,我这边也是难以排查是自身网络问题,还是服务器又或者客户门店带宽问题。
回应:
多重问题造成的现象,难以排查,无法明确责任人,也就会出现典型的互相甩锅,结果查到最后会发现人人都有责任,没有一方是无辜的。经理让前端排查问题,前端回应她能播放,没有问题,是视频流开发的问题。找视频流开发,看都不看,直接讲客户门店带宽不够,网络差之类。那客户这边回应门店是百M上传带宽,质疑服务器带宽偷工减料导致的大屏播放差,ios无法播放更是让他们质疑我方的能力。

==》第一次介入客户端问题

视频的播放实现本来无关web后端开发,并且也没有意识到严重性,由于迟迟未解决,我开始介入直播的开发。
排查:
首先观察Grafana,下行带宽和上行带宽不对等,上行带宽周期性地撑爆服务器界限,且是下行带宽的好几倍。视频服务器提供的连接数统计接口查询明显异常,极端情况下出现过一路摄像头存在50个连接数的现象。

可得客户端有问题(流量走向:门店 =》服务器 =》客户端)。

检查项目,由于特殊的请求移动端难以捕获,安卓采用pc谷歌浏览器开发者工具的Surface Duo模式模拟,ios采用macos的safari模拟,发现客户端存在大量问题。

① 视频流服务提供了一个close接口来关闭流,而前端只有pc切换关闭时调用了close接口,ws重连和小程序那边都没有调用close。

② pc和安卓(ws-flv)有大量的重连(ws连接重连),重连的旧连接仍然占用带宽,一分钟后才会自动停止。

③ ios四倍服务器带宽占用。ios每次播放虽然只有请求一个m3u8,但是会刷双倍的ts文件,并且返回上一级会挂一份ts在后台,反反复复,挂在后台的ts请求越来越多,将会撑死带宽,而且每份ts视频流服务器带宽监控显示再次翻倍。

④ 自研大屏有1 * 1、2 * 2、3 * 3、4 * 4等不同规格的通道展示,而每次切换的时候,会发现漏了几路请求在后台,有多有少,手动切换路数,ws请求会越来越多。
解决:
接着,就是一番对客户端开发的整顿。
① 借用8.5后版本的postman测试websocket,disconnect后ws状态变为DISCONNECTING,但是数据还在传输,必须关闭标签才没有流量占用,又或者等待一分钟状态变为DISCONNECTED。
在这里插入图片描述
postman的disconnect和js的ws.close()相当,这是一种优雅的关闭方式,客户端申请服务器关闭,状态转为关闭中,待服务器善后完毕才会双向关闭ws连接,所以说前端插件flv.js里面其实是有调用ws.close()的,但是由于视频流服务器存在问题,所以没有立马关闭。我特地写了个springboot整合的wsserver测试,发现DISCONNECTING状态下,数据是不可传输的,这个问题抛给了视频流开发。这个没有解决,然后是采用修改flv.js,关闭重连等都调用close接口。同时前端检查项目,需要调用关闭的地方通通调用close方法。

② 这一点其实包含在①中,由于视频流开发存在某种问题导致的ws.close()后1分钟仍然占用带宽,视频流开发是修改了flv.js通过调接口的方式立马关闭流。至于频繁重连的问题,当时是认为门店带宽问题,此时未作处理,后面讲。

③ 处理ios问题了,4倍带宽占用?并且每播放一路就会漏2倍带宽在后台占用。。。。难怪ios问题更严重,并且每当别人说不行,反馈回来已经隔了很久了,你去测试播放又可以了。
首先解决服务器双倍的问题,实时监控服务器带宽异常发给视频流开发,初始是抵赖的,理由是这个监控带宽是本地带宽未过公网,正常统计,我便使用wireshark抓包,有大量的请求是form-本机内网ip,destination-本机公网ip,可得在转hls的内部处理中有ip配错成公网ip了,导致双倍带宽占用,修改即可。
再看客户端双倍的问题,由于VConsole只统计xhr内容,前端只看见了一次播放一个请求m3u8列表便认为自己没问题,事不关己,无论发啥都不看。我便介入排查,演示ts请求异常,以及修复效果,如下两段代码,可能是hls的特殊性,另写了一个动态追加html。但是我测试发现,第一段代码的 < source> 部分删除后,双倍现象消失了。

<div id="player-wrapper" v-show="streamMode === 'hls' && brandId === 'XXX'">
	<video
    id="player-hls"
    class="video-js vjs-default-skin vjs-big-play-centered auto-play"
    controls="controls"
    autoplay="autoplay"
    x-webkit-airplay="true"
    x5-video-player-fullscreen="false"
    preload="auto"
    playsinline="true"
    webkit-playsinline="true"
    x5-video-player-type="h5"
    muted>
		<source type="application/x-mpegURL" :src="cgStreamUrl">
	</video>
</div>
playing(option) {
    
    
    const {
    
     streamMode, domId, playAddress, thumbnailUrl, callback } = option;
    if (streamMode === 'hls') {
    
    
      $('#player-wrapper').empty();
      $('#player-wrapper').eq(0).append(`
        <video id="${
      
      domId}"
        style="object-fit: fill;"
        width="100%" height="100%"
        controls="controls" autoplay="autoplay"
        x-webkit-airplay="true" x5-video-player-fullscreen="true"
        preload="auto" playsinline="true" webkit-playsinline="true"
        x5-video-player-type="h5" muted="true"
        poster=${
      
      thumbnailUrl}>
        <source type="application/x-mpegURL" src="${
      
      playAddress}">
      </video>`);
      const video = document.getElementById(domId);
      video.play();
      callback && callback();
      return null;
    }

④ 第四点可以锁定前端问题,明确了责任,后续一番优化后有所好转,但仍未根除。

■ 2.初步优化客户端后续

解决了各种异常占用带宽的问题后,服务器带宽明显富余,同步几十路播放都不成问题,迎来客户再次吐槽。
客户反馈:
有的时候还行,有的时候花屏卡顿?门店百M上行带宽,再次怀疑视频流服务器流量混用于其它服务,导致播放异常。
复现:
经理测试,白天播放相对正常,晚上开始花屏、绿屏、ws不停地各种重连,导致画面各种跳动,绿屏最为恶心,因为实在太明显了,这种情况只在pc上出现,而安卓小程序相对正常。我使用火狐浏览器测试发现,火狐浏览器相对不同,播放正常多了,不会出现绿屏,只会有少量的模糊画质,也没有了重连。而安卓的微信小程序webview效果大概和火狐浏览器差不多。
回应:
当时这个确实没辙,网络?设备?服务?仍是无法定责,结果自然会是接着甩锅。视频流开发抓包测试udp丢包率百分之四五十,仍然回复是客户网络问题,客户回复网络没有问题,不信自己测试。

==》第二次介入取流协议问题

经理和实施对门店带宽进行了测速,在线web测速,点对服务器ftp测速,点对服务器http测速(我搭了一个speedtest)。发现晚上网络确实不稳定,会比白天差些,但带宽最差也足够支持住9路并行,更不用说测试的时候看一路都卡,这明显说明不是网速问题。
排查:
再次观察Grafana,上下行带宽对等了,相对正常了且服务器带宽富余,但是网络曲线抖动得非常厉害,说明确实经常播放异常。然后让现场测试一下网络抖动,发现这个值明显偏大(非电信宽带的门店)。
解决:
后续门店取流改用tcp,播放正常了。滑稽的是视频流开发方的大佬早在16年就写了一篇关于udp花屏的博客。然后视频流开发优化无果。我仍然认为是视频流开发存在问题,它没有针对异常网络作出一定的优化,只有电信带宽才算正常带宽的标准,这显然是不负责的,改用tcp播放没有相对udp明显的延迟更说明了服务的问题。
注:
网络抖动大时,udp会出现丢包 、花屏、绿屏等现象。tcp不存在丢包,表现为卡住,然后继续播放的现象。

■ 3.改用TCP后续

改用tcp后,播放正常了,效果简直是天差地别。
客户反馈:
有某些通道掉线,有nvr假死,通道掉线为现场问题,nvr假死未知情况,现场那边重启恢复了,多次重现折腾死了技术支持,直到周末迎来爆发,nvr陆续假死,吓的不敢讲话,还好客户没有发现,并且假死一两天后有的会自动恢复。
复现:
掉线的全部都是改用tcp的门店,在此之前,从未有过这种情况。并且当时仍使用udp的门店正常,使用电信宽带的门店正常。便怀疑是改用tcp引来的问题。
回应:
视频流开发回应无从下手,建议找设备厂商解决。
排查:
使用wireshark抓包,发现tcp挥手经常只有单向断开。
例如:服务器=FIN=》门店······门店=RST,ACK=》服务器
解决:
怀疑是阻塞问题,海康的设备更新了一下设备软件版本,后面没再掉线。抓包为
服务器=FIN=》门店······门店=RST,ACK=》服务器······门店=RST=》服务器
注:
正常四次挥手
A=FIN=》B······B=ACK=》A······B=FIN=》A······A=ACK=》B
挥手回应Ack=挥手请求Seq+1

■ 4.初验

大致没问题了才引来初验
客户反馈:
大屏经常有一路不能播,但是刷新正常;有一家门店频繁跳帧。
复现:
① 报错Cannot read property 'flushStashedSamples' of null
② 因为可以远程操作的是另一家门店,我作了调试,其它门店的海康摄像头也时常跳帧,小牌摄像头反而正常,我修改该摄像头一些参数,竟然正常了。
解决:
① 寻找视频流开发做出优化重连。
② 然而现场修改该门店的摄像头参数无效,最后重启设备正常了许多。结论就是修改参数可以优化不怎么严重的跳帧。而类似该门店客流量大,一旦画面变化大就跳帧,重启设备得以修复,也是没有找到原因。

■ 5.再次优化

以上问题解决后,也就差不多了,可以慢慢调优了。
现象:
前面说过手动切换路数,ws请求会越来越多。
复现:
自研大屏频繁切换路数,客户大屏快速切换门店,小程序快速切换摄像头,pc打开播放界面快速关闭弹窗,都会出现连接挂在后台,并且不同于重连,重连是有调用ws.close()的,就算没调接口也会一分钟自动关闭,而此处不会自动关闭。
回应:
从最初开始前端就有关于这个的优化,自研大屏切换路数挂后台的概率变低了,但是仍然经常复现。比如9路切换本来会挂大半在后台,现在只会挂小几路,然后回应已修复。

==》再次介入链式没有及时关闭问题

其实挂的路数变少了,是前端修复了一些没有close场景。还在进行链式的时候X掉了窗口,链式还会继续执行才是问题所在。通俗讲就是播放函数请求打开通道,这时候客户端关掉了播放界面,会调用销毁player,因为打开通道的请求还没返回,player还没生成,所以报错,且没有结束后续播放请求,浏览器就在后台默默执行完播放函数,也就占用了带宽。
解决:
修改为可取消的Promise,
直接上代码

工具类httpRequest.js
将参数引出

http.CancelToken = axios.CancelToken

修改播放函数,设置为外界可取消,可通过reject取消,也可通过中断http请求来取消,此处使用中断http请求

playLive(id, mode = 'rtsp', transporttype = 'tcp') {
    
    
	// let hasCanceled_ = false;
    let source = http.CancelToken.source();
	const wrappedPromise = new Promise((resolve, reject) => {
    
    
        http({
    
    
                url: http.adornUrl('。。。openstream'),
                method: 'get',
                cancelToken: source ? source.token : '',
                params: {
    
    
            id,
                    transporttype,
                    mediatype: 'video',
                    format: mode,
        },
	}).then((response) => {
    
    
                let data = response.data;
        if (data.PlayAddress !== 'Stream not exist') {
    
    
            resolve(data);
        } else {
    
    
            reject(response);
        }
	})
    })
    return {
    
    
            promise: wrappedPromise,
            cancel() {
    
    
        // hasCanceled_ = true;
        source.cancel('取消请求');
    },
	};
},

调用播放函数,如果函数中断,then后序不会执行。

this.vPromise = Video.playLive(paramId, 'rtsp', transporttype);
this.vPromise.promise.then((data) => {
    
    
。。。

然后在销毁方法里添加这个中断方法即可。

vPromise.cancel();

效果如图
在这里插入图片描述
这样就避免了那些莫名其妙多出来的连接。

■ 6.视频流鉴权问题

参考 视频直播鉴权结合业务系统的token或session

最后

到目前为止,相对稳定了,也能说得过去,运维也相对容易,宏观Grafana带宽可得大体播放情况,设备是否异常,局部使用ELK统计超时打开通道的请求,可以马上定位异常通道。

猜你喜欢

转载自blog.csdn.net/qq_24054301/article/details/120085579