首先,先描述下需求吧。因业务需要需要将海康威视摄像头获取到的视频流展示至网页端…听起来很简单对不对?Naive!三周了!你知道这三周我都是怎么过来的吗!
其次,感谢一下三周以来陪我一起Debug的祁昆仑老师,天知道如果我一个人搞这个得花多长时间。
最后吐槽一下海康威视做的SDK,要么好好写文档,要么好好测试做Debug,贵司的产品真的不好用…
仅以此博文记录踩过的坑,以及流逝的三周岁月。
1.海康威视Web SDK(有插件版的和无插件版的)
海康威视有插件版的SDK,用不起来我认为很大程度上海康威视的写文档的和开发人员要背锅,没有详细的文档,甚至得靠自己Debug js代码……
有插件版的SDK,下载完成解压后->运行其中的exe安装视频插件->进入Demo->在Demo中输入IP、帐号、密码。就会出现第一个问题:
{项目文件夹}/ISAPI/Security/sessionLogin/capabilities?username=admin Failed to load resource: the server responded with a status of 404 (Not Found)
当然这个错误十分好理解,项目文件夹下的确是没有这么大串所描述的文件或者文件夹的;理应我们也不应该向这个文件夹进行这个请求,而应当向我们输入的IP地址进行请求。为什么会这样呢,在检查了源码经过尝试后,我们发现当不进行任何参数的传递时,的的确确是向根目录进行请求的。(在这里怀疑一下这个SDK直接用在了录像柜后台)
那我们传的参数去哪里了呢?总之检查过了Demo里的代码,没有问题;尝试直接在js库文件中将localhost修改为请求的IP出现了跨域问题;最后在搜索你nginx 海康威视后,发现海康威视有自带Nginx的Web SDK以及Demo,它还不需要插件!
下载解压无插件版Web SDK->启动Nginx->访问Demo->在Demo中输入IP、帐号、密码,然后…然后出现了新的问题。
:/ISAPI/Security/sessionLogin/capabilities?username=admin Failed to load resource: the server responded with a status of 400 (too many headers)
在各种面向百度、谷歌、StackoverFlow编程无果后,事情变得有点绝望。接着我突然想到自己前面的猜测——这个sdk可能录像柜80端口也在使用。使用浏览器通过对网络进行记录发现:确实无插件版的SDK中的请求Cookies比录像柜中的请求的Cookie多出不止一项。
在各种搜索引擎的帮助下,定位到了nginx的配置上,在nginx对请求进行转发时加了一堆没有必要的头,注释掉这些头即可。
(ps:最新的版本中已修复这一问题)
注释完没有必要的头之后能够成功登录,但你翻过了一座山还有另一座山等着你,爬过一个坑你就会踩进另一个坑…是的我们的设备不支持Socket取流。
2.RTSP取流FFMPEG转流RTMP浏览器播放
咨询了海康威视的客服(在这里夸一下客服,回复的还蛮及时)后,海康威视的客服给出了这个解决方案,在继续面向搜索引擎编程的指引下,成功地完成了第一步尝试。具体步骤如下:
首先下载nginx Gryphon,这个版本非常贴心地内置了ffmpeg的模块;解压,修改nginx-win.conf的sever如下:
server {
listen 8088;
server_name localhost;
add_header Access-Control-Allow-Origin *;
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root nginx-rtmp-module/;
}
location /control {
rtmp_control all;
}
rtmp修改如下:
rtmp {
server {
listen 1935;
chunk_size 4000;
application live {
live on;
}
}
}
第二步下载ffmpeg,进入bin目录,使用以下命令进行转码:
ffmpeg -re -rtsp_transport tcp -i "rtsp://admin:{password}@{ipaddress}:554/Streaming/Channels/101?transportmode=unicast" -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 "rtmp://localhost:1935/live/test2"
其中password处换成自己的密码,ip地址处换成海康威视设备的ip;”rtmp://localhost:1935/live/test2”中的1935已在conf内配置;test2是视频流的临时地址。
第三步在nginx Gryphon内新建test.html,写入如下代码:
<html>
<head>
<title>video</title>
<!-- 引入css -->
<link href="//cdn.bootcss.com/video.js/7.0.0-alpha.1/alt/video-js-cdn.css" rel="stylesheet" />
</head>
<body>
<div class="videoBox">
<video id="my_video_1" class="video-js vjs-default-skin" controls>
<source src="http://192.168.1.129:8088/video/test.m3u8" type="application/x-mpegURL">
</video>
</div>
</body>
</html>
<script src="//cdn.bootcss.com/video.js/7.0.0-alpha.1/video.min.js">
</script>
<script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>
</script>
<script> videojs.options.flash.swf = "./videojs/video-js.swf"; var player = videojs('my_video_1', {"autoplay":true}); player.play();
</script>
第四步使用win-conf启动Nginx。
然后就大功告成啦~才怪。
Chorme等一众主流浏览器已停止了对flash的支持因此无法用这种方法进行视频的转流播放,查找后决定使用苹果的方案。将命令改成:
ffmpeg -re -rtsp_transport tcp -i "rtsp://admin:{password}@{ipaddress}:554/Streaming/Channels/101?transportmode=unicast" -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 "F:/nginx/html/video/test.m3u8"
html中的视频地址改为:
<script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>
启动nginx,大功告成,撒花!
3.flvjs
上面的方案挺好的,就是不太好。
摄像头采集的画面转码后分辨率极低,并且采集到的画面如果有大幅度的晃动会直接花屏,另外非常不能接受的是它的延迟——虽然可能也和我的电脑配置有关——长达半分钟。在祁老师的激情调研下,决定采用这一节描述的方案。
使用ffmpeg将RTSP转为flv,通过websocket取流后,使用flvjs对视频流再播放。
这里直接使用找到的项目,项目地址:https://github.com/LorinHan/flvjs_test
将整个项目扒下来,解压。在项目的根目录安装fluent-ffmpeg,websocket-stream。
npm install express express-ws fluent-ffmpeg websocket-stream -S -D
修改index.js将ffmpeg的目录放置替换ffmpeg.setFfmpegPath中的路径。
ffmpeg.setFfmpegPath(put_your_ffmpeg_path_here);
使用node index.js启动项目。
在front/src/component中修改HellWorld.vue中的rtsp地址。
data () {
return {
id: "1",
rtsp: "put_your_rtsp_path_here",
player: null
}
},
回到front,运行npm install安装相关依赖,安装完毕后运行npm run dev 运行项目,在浏览器输入vue项目的地址,你就能在你的浏览器中欣赏到一片纯白。
fluent-ffmpeg报错:
ffmpeg exited with code 1
出现这个问题是因为ffmpeg传入的参数不对,在看了stackoverflow等论坛的相关问题的相关描述以及相关的解决方案后找到了ffmpeg-fluent的项目地址,在项目地址中恰好有websocket取流的实例,参照示例将代码修改如下:
var express = require("express");
var expressWebSocket = require("express-ws");
var ffmpeg = require("fluent-ffmpeg");
ffmpeg.setFfmpegPath("C:/ffmpeg/bin/ffmpeg");
var webSocketStream = require("websocket-stream/stream");
var WebSocket = require("websocket-stream");
var http = require("http");
function localServer() {
let app = express();
app.use(express.static(__dirname));
expressWebSocket(app, null, {
perMessageDeflate: true
});
app.ws("/rtsp/:id/", rtspRequestHandle)
app.listen(8888);
console.log("express listened")
}
function rtspRequestHandle(ws, req) {
console.log("rtsp request handle");
const stream = webSocketStream(ws, {
binary: true,
browserBufferTimeout: 1000000
}, {
browserBufferTimeout: 1000000
});
let url = req.query.url;
console.log("rtsp url:", url);
console.log("rtsp params:", req.params);
ffmpeg('rtsp://admin:{password}@{ip}:554/Streaming/Channels/201?transportmode=unicast', { timeout: 432000 })
.preset('flashvideo')
.addOption('-hls_time', 1)
.addOption('-hls_list_size',10)
.on('end', function() {
console.log('file has been converted succesfully');
})
.on('error', function(err) {
console.log('an error happened: ' + err.message);
}).noAudio().pipe(stream);
}
localServer();
再重新启动,成功!
说明下这里的参数,其中preset(‘flashvideo’)是指使用fluent-ffmpeg中以内置的预设即等同于:
ffmpeg
.format('flv')
.flvmeta()
.size('320x?')
.videoBitrate('512k')
.videoCodec('libx264')
.fps(24)
.audioBitrate('96k')
.audioCodec('aac')
.audioFrequency(22050)
.audioChannels(2);
-hls_time指的是缓存时间,即缓存到多少s开始播放;hls_list_size为缓存列表,表示最多缓存多少个hls。
##
4.总结
在flash插件各大主流浏览器不在支持的情况今天实现rtsp流播放的方式有两种:1.RTSP取流FFMPEG转m3u8推流;2.使用ffmpeg将RTSP转为flv,通过websocket取流后,使用flvjs对视频流再播放。