Hikvison对接NVR实现WEB无插件开发包实现前端视频预览(htmlvuenginx代理)

Posted 霸道流氓气质

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hikvison对接NVR实现WEB无插件开发包实现前端视频预览(htmlvuenginx代理)相关的知识,希望对你有一定的参考价值。

场景

Vue中预览HIKVSION海康威视的NVR(网络硬盘录像机)中多个通道(摄像机)的视频:

Vue中预览HIKVSION海康威视的NVR(网络硬盘录像机)中多个通道(摄像机)的视频_霸道流氓气质的博客-CSDN博客_海康nvr网页预览

在上面进行NVR视频预览时采用的是WEB控件开发包,需要电脑安装插件,并且需要浏览器在

兼容模式下预览。

除此之外,还有另一种无插件开发包的方式

 

截止目前是WEB无插件开发包V3.2

WEB3.2无插件版本开发包,支持高版本谷歌、火狐浏览器,同时需要设备支持Websocket取流。无插件版本需要使用nginx代理服务器。

按照说明可知,需要NVR支持websocket取流。

验证NVR是否支持websocket取流

这里的NVR型号为:DS-8664n-k16

 

登录到NVR的web页面-配置-系统设置-网络-高级配置-启用websocket

 

如果有启用WebSokcet选项,则代表可以,为了进一步验证,下载上面的官方demo。

 

按照官方提供的说明,运行nginx的代理

 

修改nginx目录下的配置文件

 

这里只是简单修改了端口号为8000

然后点击start.bat启动nginx,访问

http://127.0.0.1:8000/cn/demo.html

输入NVR对应的地址、用户名、密码等信息,然后点击登录和开始预览

 

如果官方demo能预览成功,那么就可以使用无插件开发包了。

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

1、在demo.html中能实现预览的基础上,只需要根据自己的需要改造即可,

对应的demo以及接口文档说的很清楚。

那么怎么将demo的示例与现有项目进行结合,比如前后端分离的SpringBoot+Vue的项目。

若依前后端分离版本地搭建开发环境并运行项目的教程:

若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_前后端分离项目本地运行

在基于Vue的项目上进行代码改造

 

后台存储摄像头的相关信息,最需要的就是通道号,拿其来进行预览时传参用

 

选中摄像头时点击预览按钮,传递通道号参数至预览页面。

前面流程可参考

SpringBoot+Vue+HIKVSION实现摄像头多选并多窗口预览(插件版):

SpringBoot+Vue+HIKVSION实现摄像头多选并多窗口预览(插件版)_霸道流氓气质的博客-CSDN博客_websocket取流

然后根据官方demo引入需要的js等文件

 

下面主要是预览页面的代码

<template>
  <el-dialog
    title="视频监控"
    :visible.sync="videoOpen"
    width="800px"
    :append-to-body=false
    @close="videoClose"
    class="video_box"
    :modal=false
  >
    <!-- 摄像头 -->
    <!--视频窗口展示-->
    <div id="playWnd" class="playWnd" ref="playWnd"></div>
  </el-dialog>
</template>
 
<script>
const g_iWndIndex = 0; //可以不用设置这个变量,有窗口参数的接口中,不用传值,开发包会默认使用当前选择窗口
export default 
  name: "HkVideo1",
  components: ,
  props: 
    channelId: "",
  ,
  watch: ,
  data() 
    return 
      isLogin: false,
      videoOpen: false,
      szDeviceIdentify: "", // 设备标识(IP_Port)
      ip: "NVR的ip",
      port: "80",
      username: "NVR的用户名",
      password: "NVR的密码",
    ;
  ,
  created() ,
  mounted() ,
  destroyed() ,
  methods: 
    // 创建播放实例
    async initPlugin() 

      let iRet = window.WebVideoCtrl.I_CheckPluginInstall();

      if (-1 == iRet) 
        alert("您还未安装过插件,请安装WebComponentsKit.exe!");
        this.$confirm("是否下载WebComponentsKit.exe插件?", "提示", 
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        ).then(() => 
          window.location.href = "/static/HK_3.2/WebComponentsKit.exe";
        );

        return;
      
      // 初始化插件参数及插入插件
      window.WebVideoCtrl.I_InitPlugin(800, 600, 
        bWndFull: true, //是否支持单窗口双击全屏,默认支持 true:支持 false:不支持
        iPackageType: 2,
        //szColorProperty:"plugin-background:0000ff; sub-background:0000ff; sub-border:00ffff; sub-border-select:0000ff",   //2:PS 11:MP4
        iWndowType: 1,
        bNoPlugin: true,
        // 窗口选中事件回调
        cbSelWnd: (xmlDoc) => 
          var szInfo = "当前选择的窗口编号:" + g_iWndIndex;
          console.log(szInfo);
        ,
        // 窗口双击事件回调
        cbDoubleClickWnd: (iWndIndex, bFullScreen) => 
          var szInfo = "当前放大的窗口编号:" + iWndIndex;
          if (!bFullScreen) 
            szInfo = "当前还原的窗口编号:" + iWndIndex;
          
          console.log(szInfo);
        ,
        // 插件事件回调
        cbEvent: (iEventType, iParam1, iParam2) => 
          if (2 == iEventType) 
            // 回放正常结束
            console.log("窗口" + iParam1 + "回放结束!");
           else if (-1 == iEventType) 
            console.log("设备" + iParam1 + "网络错误!");
           else if (3001 == iEventType) 
            this.clickStopRecord(g_szRecordType, iParam1);
          
        ,
        cbRemoteConfig: () => 
          console.log("关闭远程配置库!");
        ,
        // 插件初始化完成回调
        cbInitPluginComplete: () => 
          this.$nextTick(() => 
            console.log('窗口', this.$refs.playWnd)
            let isInit = window.WebVideoCtrl.I_InsertOBJECTPlugin('playWnd');
            console.log('isInit', isInit)
 
            // 检查插件是否最新
            if (-1 == window.WebVideoCtrl.I_CheckPluginVersion()) 
              alert("检测到新的插件版本,请对WebComponentsKit.exe进行升级!");
              return;
             else this.clickLogin();
          )
        ,
      );
    ,

    // 登录
    clickLogin() 
      let  ip, port, username, password  = this;

      if ("" == ip || "" == port) 
        return;
      

      this.szDeviceIdentify = ip + "_" + port;

      let iRet = window.WebVideoCtrl.I_Login(ip, 1, port, username, password, 
        success: (xmlDoc) => 
          setTimeout(() => 
            this.getChannelInfo();
            this.getDevicePort();
          , 10);
        ,
        error: (status, xmlDoc) => 
          console.log(" 登录失败!", status, xmlDoc);
        ,
      );

      if (-1 == iRet) 
        this.clickStartRealPlay();
      
    ,
    // 获取通道
    getChannelInfo() 
      if (null == this.szDeviceIdentify) 
        return;
      

      // 模拟通道
      window.WebVideoCtrl.I_GetAnalogChannelInfo(this.szDeviceIdentify, 
        async: false,
        success: (xmlDoc) => 
        ,
        error: (status, xmlDoc) => 
          console.log(" 获取模拟通道失败!");
        ,
      );
      // 数字通道
      window.WebVideoCtrl.I_GetDigitalChannelInfo(this.szDeviceIdentify, 
        async: false,
        success: (xmlDoc) => 
        ,
        error: (status, xmlDoc) => 
          console.log(" 获取数字通道失败!");
        ,
      );
      // 零通道
      window.WebVideoCtrl.I_GetZeroChannelInfo(this.szDeviceIdentify, 
        async: false,
        success: (xmlDoc) => 
        ,
        error: (status, xmlDoc) => 
          console.log(" 获取零通道失败!");
        ,
      );
    ,
    // 获取端口
    getDevicePort() 
      if (null == this.szDeviceIdentify) 
        return;
      

      this.port = window.WebVideoCtrl.I_GetDevicePort(this.szDeviceIdentify);
      if (this.port != null) 
        this.clickStartRealPlay();
        return true
       else 
        console.log(" 获取端口失败!");
        return false
      
    ,
    // 开始预览
    clickStartRealPlay(iStreamType) 
      let wndInfo = window.WebVideoCtrl.I_GetWindowStatus(g_iWndIndex);

      let iChannelID = this.channelId; // 通道列表
      let bZeroChannel = false; // 是否播放零通道(下拉框)
      let szInfo = "";

      if ("undefined" === typeof iStreamType) 
        iStreamType = 2; // 1主码流 2子码流 3第三码流 4转码码流
      

      if (null == this.szDeviceIdentify) 
        return;
      
      let startRealPlay = () => 
        window.WebVideoCtrl.I_StartRealPlay(this.szDeviceIdentify, 
          iRtspPort: 554,
          iStreamType: iStreamType,
          iChannelID: iChannelID,
          bZeroChannel: bZeroChannel,
          success: () => 
            szInfo = "开始预览成功!";
            console.log(szInfo);
          ,
          error: (status, xmlDoc) => 
            if (403 === status) 
              szInfo = "设备不支持Websocket取流!";
             else 
              szInfo = "开始预览失败!";
            
            this.$message.error(szInfo);
          ,
        );
      ;

      if (wndInfo != null) 
        // 已经在播放了,先停止
        window.WebVideoCtrl.I_Stop(
          success: () => 
            startRealPlay();
          ,
        );
       else 
        startRealPlay();
      
    ,
    // 停止预览
    clickStopRealPlay() 
      let oWndInfo = window.WebVideoCtrl.I_GetWindowStatus(g_iWndIndex),
        szInfo = "";

      if (oWndInfo != null) 
        window.WebVideoCtrl.I_Stop(
          success: () => 
            szInfo = "停止预览成功!";
            console.log(szInfo);
          ,
          error: () => 
            szInfo = "停止预览失败!";
            console.log(szInfo);
          ,
        );
      
    ,
    // 全屏
    clickFullScreen() 
      window.WebVideoCtrl.I_FullScreen(true);
    ,
    // 停止录像
    clickStopRecord(szType, iWndIndex) 
      if ("undefined" === typeof iWndIndex) 
        iWndIndex = g_iWndIndex;
      
      var oWndInfo = window.WebVideoCtrl.I_GetWindowStatus(iWndIndex),
        szInfo = "";

      if (oWndInfo != null) 
        window.WebVideoCtrl.I_StopRecord(
          success: () => 
            if ("realplay" === szType) 
              szInfo = "停止录像成功!";
             else if ("playback" === szType) 
              szInfo = "停止剪辑成功!";
            
            showOPInfo(oWndInfo.szDeviceIdentify + " " + szInfo);
          ,
          error: () => 
            if ("realplay" === szType) 
              szInfo = "停止录像失败!";
             else if ("playback" === szType) 
              szInfo = "停止剪辑失败!";
            
            showOPInfo(oWndInfo.szDeviceIdentify + " " + szInfo);
          ,
        );
      
    ,
    // 查看摄像
    videoChange() 
      this.videoOpen = true;
      this.$nextTick(() => 
        if(!this.isLogin) 
          this.isLogin = true
          this.initPlugin()
         else 
          this.clickStartRealPlay();
        
      );
    ,
    // 关闭摄像头弹窗
    videoClose() 
      this.videoOpen = false;
      console.log(this.isLogin)
      this.clickStopRealPlay();
    ,
  ,
;
</script>
  <style scoped lang="scss">
.video_box 
  width: 100%;
  height: 100%;


.plugin 
  width: 100%;
  height: 100%;


.playWnd 
  width: 800px;
  height: 600px;
  margin: 0;


.video_box 
  ::v-deep .el-dialog__body 
    padding: 0 !important;
  

</style>

2、关于nginx代理

查看官方示例nginx配置文件中

 

需要做两部分的代理。最前面的

        location / 
            root   "../webs";
            index  index.html index.htm;
        

是其示例demo的静态页面的代理,对应自己的Vue的dist包的代理

对应的线上要改成

    location / 
      root C:\\dist;
      try_files $uri $uri/ /index.html;
      index index.html index.htm;
    

剩下两个代理一个是接口代理,比如调用登录接口时进行反向代理

​
 location ~ /ISAPI|SDK/ 
     if ($http_cookie ~ "webVideoCtrlProxy=(.+)") 
  proxy_pass http://$cookie_webVideoCtrlProxy;
  break;
     
 

​

这个意思大概是如果是ISAPI或者SDK开头的请求,波浪线代表不忽略大小写,就被被代理到下面的地址。

但是这里会疑问为什么代理配置文件中没有配置任何关于nvr的ip地址的配置,那我代理时是怎么请求接口的。

这里是在中间加一个nginx进行转发,nginx通过请求中的Cookie找到NVR的Ip地址进行转发,包括预览时获取视频流

也是通过这种方式进行websocket代理并获取视频流。

类似如下这张流程图。

 

所以只要按照官方的nginx的配置文件将自己项目的nginx配置文件进行修改即可

下面提供一个改造之后项目的nginx的配置文件示例

​
worker_processes 1;
events 
  worker_connections 1024;

http 
  include mime.types;
  default_type application/octet-stream;
  sendfile on;
  keepalive_timeout 65;
  map $http_upgrade $connection_upgrade 
    default upgrade;
    '' close;
  
  server 
    listen 90;
    server_name localhost;

    client_max_body_size 300M;

    #websocket相关配置
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-real-ip $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;

   
    location / 
      root D:\\font\\dist;
      try_files $uri $uri/ /index.html;
      index index.html index.htm;
    
    location /prod-api/ 
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header REMOTE-HOST $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_pass http://服务器ip:8888/;
    
    location ~ /ISAPI|SDK/ 
      if ($http_cookie ~ "webVideoCtrlProxy=(.+)") 
 proxy_pass http://$cookie_webVideoCtrlProxy;
 break;
      
    

    location ^~ /webSocketVideoCtrlProxy 
     #web socket
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
     proxy_set_header Host $host;

     if ($http_cookie ~ "webVideoCtrlProxyWs=(.+)") 
  proxy_pass http://$cookie_webVideoCtrlProxyWs/$cookie_webVideoCtrlProxyWsChannel?$args;
  break;
     
     if ($http_cookie ~ "webVideoCtrlProxyWss=(.+)") 
  proxy_pass http://$cookie_webVideoCtrlProxyWss/$cookie_webVideoCtrlProxyWsChannel?$args;
  break;
     
    

    error_page 500 502 503 504 /50x.html;
    location = /50x.html 
      root html;
    
  


​

前后端项目使用Nginx代理可以参考

若依前后端分离版本,Windows下使用Nginx代理的方式进行部署(全流程,图文教程):

若依前后端分离版本,Windows下使用Nginx代理的方式进行部署(全流程,图文教程)_霸道流氓气质的博客-CSDN博客

预览效果

 

Windows远程桌面实现之十二:桌面屏幕通过ONVIF协议与NVR等监控录像设备对接,以及进一步增强直播功能

by fanxiushu 2021-10-27 转载或引用请注明原始作者。

标题还是取名叫 “Windows远程桌面实现“,
其实经过全面的移植,xdisp_virt程序已经支持 macOS系统,各类linux发行版,
(iOS也移植了,只是发布iOS的程序实在是麻烦,所以并没发布出来,
暂时也没有对应的Android手机,所以Android系统暂时也没有移植)
所以xdisp_virt已经不再是单一的Windows版本的程序了,标题中还保持”Windows“,是因为讲述这个系列的文章比较多。

本文讲述在xdisp_virt程序中添加新的功能:ONVIF监控类控制协议。
没有做过安防监控类的人,可能对ONVIF协议比较陌生,其实我也没在安防监控类的行业中混过。
之所以想起在xdisp_virt中添加ONVIF,一是有江郎才尽之感,都不知道还能在xdisp_virt中添加什么功能,
二是有人询问xdisp_virt是否能添加ONVIF功能,记得最早是好像两三年前就有人问过,
只是当时无暇顾及,而且感觉在当时做的远程桌面中添加ONVIF会显得不伦不类,
可是当把直播功能加进xdisp_virt之后,也就不再这么想了(反正现在已经是个集成多种功能的大杂烩了)。

说起安防监控,大部分人可能陌生,但是却处处出现在我们的身边,可以说是无孔不入。
比如马路上的大大小小的各种功能的摄像头,用于监控各种车辆的通行状况,
就连我们生活的小区里的公共区域里,也会看到各类摄像头。
电梯里的摄像头,楼层过道里的摄像头,公交车里的摄像头,商场里的摄像头,等等。。。
以上列举的只是一部分的情况,而且是那种正规的合法的使用场所。
还有许多不那么正规,甚至是违法使用的地方,
比如酒店房间里偷偷安装摄像头,比如厕所里安装摄像头,比如比较变的老板在公司里安装摄像头监控员工,等等。。。
不管怎么说,我们能直观感受到是摄像头这么一个实物,
但是摄像头摄录的视频如何处理,如何传输和保存。视频的数据量都是很大的,
即使经过各种编码压缩,视频数据量也是相当可观的,因此安防监控的核心工作是如何处理这些数据。

摄像头都是工作在第一线,他们距离控制中心的距离往往都是比较远的,
它们采集的视频如何传输,
传统的比如VGA,HDMI,USB,这些数据线可以传输原始视频信号,
但是传输的距离非常有限,顶多一两米就会出现严重信号衰减,而且造价比较贵,
比如小区里,公共区域的摄像距离监控室的距离不止几百米远,
而马路上的摄像头距离监控室就更加远了,几公里甚至几十公里都有可能。
而采用网络传输,就能解决所有这些问题,因此IPC(IP Camera)网络摄像机孕育而生。
IPC网络摄像机 = 传统摄像头 + 一个简单的嵌入式集成系统用于把视频编码成H264或H265,
 然后通过网络流媒体传输协议把视频信号传输到控制中心。IPC一般都是采用RTSP流协议传输视频和音频信号。
以上说的使用RTSP协议,只是用来传输视频和音频数据的。
还有一个重要的内容:各种控制信息如何传递,有时需要远控控制摄像头转换方向进行摄录,
而且RTSP需要播放地址的,这个RTSP地址信息如何得知,等等。。。
于是早在2008年,就有几家公司组成一个联盟,共同指定了一套控制协议,目的就是为了让各个厂商生产的IPC和控制端能互相接入。
这个控制协议就是我们这篇文件讲述的ONVIF协议。而这个控制端就是NVR(Network Video Recoder)。

有时可能有这样的一些需求,需要把工作电脑的屏幕录制下来,尤其是一些重要的操作内容。
当然我们也可以直接录制成本地录像文件。
但是录制到 NVR 更方便处理和存储,也更加安全,因此这就是在xdisp_virt中为何要实现ONVIF的原因了。
xdisp_virt只是实现RTSP服务端还不够,还需要ONVIF控制监控协议,这样才能更好的接入通用的 NVR 设备。

以前的版本的 xdisp_virt 已经实现了RTSP流协议,当时是作为直播服务端实现的其中一个协议。
完全是是自己实现的RTSP协议,并且只支持TCP传输。具体可以去查询我的这个系列文章的上一篇文章的描述。
链接地址:Windows远程桌面实现之十一:桌面屏幕通过各种直播服务端直播(RTSP, RTMP, HTTP-FLV, HLS)_fanxiushu的专栏-CSDN博客

本以为这次添加ONVIF协议不用修改RTSP代码。
但是测试发现我自己实现的RTSP协议好像在有些ONVIF测试客户端不能正常播放音视频。
(主要是使用 ONVIF Device Manger 工具进行ONVIF测试的。)
具体原因也不想去查找了,于是决定移植一个兼容性更强的RTSP服务端,于是找到了 live555 开源代码。
这是个非常古老的开源项目,10几年前就已经出现了,
但是他的代码风格并不是我喜欢的,而且可能出现的问题也比较多。

它到处都在使用C++的类,各种封装的CLASS绕来绕去的,一不留神就能把人给绕晕。
本来很简单的功能,使用纯C语言很方便就能实现和描述的。
如果不加控制的使用C++的类来描述,绝对能把最简单的功能绕成世界上最复杂的。
这就是我对这些C++的封装类最坏的印象。

现在把移植 live555发现的问题和修改办法写下来,方便看到这篇文章也在做同样移植的有一点帮助:
其实最主要的问题是出现在live555底层网络通信上(它实现的底层通讯框架实在不敢恭维)
1,首先 live555 使用select进行网络基础通讯,而且只在一个大线程里处理所有客户端的连接。
      因此不要指望live555能承担大量客户端的连接
(其实音视频数据量本来就大,能承担上百个客户端播放而不占光网络带宽就已经很不错了)。
   windows端编译live555 需要修改 FD_SETSIZE 宏,否则默认只能支持64个。linux端默认就是1024,倒不必修改。
2,OutPacketBuffer::maxSize 这个参数尽量设置大点,比如设置2M,因为现在H264编码都是很大分辨率和超清图像,
     会占用很多空间,live源码中设置的值太小。
3,RTSP默认是使用UDP传输音视频数据, 如果把 live555 改成TCP传输音视频数据,有可能长期出现花屏,
    RTPInterface::sendDataOverTCP 函数中,send有可能会失败,然后他居然再把non-block的socket转成阻塞模式再发送,
    这本身就是单线程的大循环,这么做会阻塞其他客户端的的数据的,反正它这个处理办法实在不咋地。
    为了尽量让系统自己处理发送数据,我们可以把TCP这个连接的SendBuffer设置大些,比如设置 1M大小,
    使用live提供的increaseSendBufferTo 函数,或者直接操作 setsocketopt 的 SO_SNDBUF命令。
    具体做法就是在 RTSPServer::createNewClientConnection 函数中对建立连接的clientSocket进行修改,
     或者申明一个myRTSPServer类继承自 RTSPServer 来修改 。
     增大socket的发送缓存也只是折中的办法,遇到网络不好的情况下,依然会出现很多跟网络通信相关的问题。
     不过似乎视频传输本身就需要良好的网络,否则本身就会造成视频播放的不稳定。
4,windows平台中,live并没有设置 SO_KEEPALIVE,因此同样的,我们也得在RTSPServer::createNewClientConnection 函数中进行修改。
     设置 SO_KEEPALIVE会让死连接能及时被发现和断开。linux平台倒是不用修改,live已经修改好了。
5,这个问题也比较严重, live里边 session和connect是分开的,具体就是 RTSPClientSession和RTSPClientConnection类。
     播放器连接上来之后,首先RTSPClientConnection会被建立,然后根据播放情况RTSPClientSession会被创建。
     当播放器正常停止播放时候,RTSPClientConnection和RTSPClientSession都能正常清除,
     但是当网络异常,播放器非正常退出时候,RTSPClientConnection能正常退出,但是RTSPClientSession却还在,
     并且还在傻傻的拼命的通过UDP传输数据给客户端播放器,而客户端播放器早就异常退出了。
     原本以为有个超时可以控制,结果遇到这种情况超时也没用,会一直这么传输,没有停止的迹象。
     实在没办法,只好继承RTSPClientSession和RTSPClientConnection两个类,然后记录下他们的关联关系。
     然后在RTSPClientConnect析构函数中删除掉没有正常删除的 RTSPClientSession类。

大致发现以上这些问题,基本都是关于网络通信方面的,
因此如果你移植live555,发现视频播放有问题,基本可以考虑是底层网络通信出现了问题。
当然xdisp_virt是实时直播流,而live555默认的是基于视频文件的,一个视频文件的编码格式是固定的,
而直播流可能随时在变。因此我们得重载live555里的 sdpLines 函数,自己构造动态的 SDP 描述信息。
自己构造SDP描述信息也是费时费力的事情。
经过这么一通的对live555修改,与我们自己完全开发RTSP协议能有多大差别!
只是可能自己开发RTSP有可能那些协议格式内容没有考虑到,从而造成兼容性问题。

再来看看ONVIF协议的开发。
ONVIF是基于WebService的,具体就是在HTTP协议基础上,
通过SOAP协议(类似RPC远程过程调用,建立在XML格式之上)进行通讯的。
SOAP协议也并不是我喜欢的协议格式,这玩意和XML一样,属于那种又笨重,处理起来又麻烦的东西。
而且很多简单的场所,简单的使用一行行的文本来描述就能解决的问题,非要搞个XML或者SOAP通讯充当冤大头!
这里采用开源的 gSOAP, ONVIF协议使用 官网(Home - ONVIF Mandarin)的wsdl 来生成 C语言格式的代码。
官网上的wsdl有很多,而我这里只需要 devicemgmt.wsdl,media.wsdl, remotediscorvry.wsdl 三个wsdl就可以了。

下面以linux 平台下如何生成基于wsdl的 ONVIF 代码为例。
1,首先建立 SOAP 编译环境。
     从网上下载 开源gSOAP库,放到某个目录中,比如 /home
      然后就是解压到某个目录和编译gSOAP,这里不再具体描述编译过程。
     新建一个目录,比如 /home/onvif_build
     把 gSOAP目录中custom,plugin,import三个子目录复制到 onvif_build目录中,
     再把编译生成的wsdl2h 和 soapcpp2 两个程序复制到onvif_build目录中,
      这样编译wsdl的环境基本就做好了。
 2, 通过 wsdl 生成 onvif.h头文件。在onvif_build目录下使用如下命令,
       ./wsdl2h -d -o onvif.h -c -s -t ./typemap.dat https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl https://www.onvif.org/onvif ;
/ver10/media/wsdl/media.wsdl https://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl
     这个是在线生成方式,就是wsdl直接从官网下载,推荐使用这种办法。
     -d 参数生成 __any dom相关结构,这个在ONVIF中会用到,
     -c表示生成C风格的头文件,因为还是喜欢C那种简洁的风格,所以这里添加了-c参数。
3,通过上面步骤2,就会在onvif_build目录中生成 onvif.h 头文件。接着就是通过这个头文件,生成SOAP主体代码。
     使用如下命令:
     soapcpp2 -s onvif.h -x -L -I./ -I./gsoap -I./import/ -I./custom -I./plugin
     需要去import目录下修改wsa5.h,找到SOAP_ENV__Fault这个结构体,并且直接把这个结构体注释掉,否则可能会编译失败。
     生成的 是纯C风格的代码,会生成 文件名是 soapC.c, soapClient.c,  soapServer.c, soapH.h, soapStub.h 这些源码文件。
4,组建我们自己的ONVIF工程。
      把上面生成的这些 soap*** 文件复制到我们的工程目录中,再从gSOAP目录中复制一些有用的源码到我们的工程目录中,
     gSOAP这主要包括 stdsoap2.c/h, dom.c等等这些,
     到时根据编译的时候出现哪些函数未定义来确定复制哪些gSOAP中的源码到我们的工程目录中。
      为了给ONVIF添加密钥验证,还得给编译工程添加WITH_DOM,WITH_OPENSSL宏,
     并且连接openssl库,以及需要gSOAP中对应的一些源码文件。
     
然后就是实现由wsdl描述生成的onvif协议中相应的SOAP回调函数。就光上面三个wsdl生成的函数就达到两三百个函数之多。
非常离谱,不过好在大部分都不需要去实现,只是简单重复的返回 SOAP_OK 就可以了。
可是即便这样,也需要重复去填写几百个这样的函数,实在是够奇葩的!

首先实现discovery功能,需要创建组播socket套接字,并且固定在3702端口侦听,
因为windows平台有个 FDResPub 服务也是使用同样的协议,
因此我们要么禁用FDResPub 服务,要么别在windows平台开启ONVIF的discovery功能。
之后只需要实现 __wsdd__Probe 回调函数即可,在此函数中报告本机的ONVIF相关信息。

接着实现 一些ONVIF设备通用函数,包括
__tds__GetServices, __tds__GetCapabilities,__tds__GetServiceCapabilities,__tds__GetDeviceInformation,
__tds__GetSystemDateAndTime, __tds__GetNetworkInterfaces,__tds__GetHostname 等等这些,
可以根据自己的需求分别去实现这些函数的功能。

再然后就是去实现跟媒体相关的一些函数了,主要是包括RTSP协议的URL地址,
视频源的一些信息, 视频编码比如H264等的一些信息,如果包括声音的话,还需要音频源和音频编码信息。
这里也不再赘述了,反正就是一些非常枯燥乏味的控制信息的填写。
本来ONVIF还包括PTZ云台(控制摄像头转向的)等控制命令的,
但是xdisp_virt实现的是桌面屏幕,不存在云台的概念。

开发过程中可以辅助 ONVIF的 ONVIF Device Test Tool 工具查看每个ONVIF请求的准确性,
也可以使用 ONVIF Device Manager 工具来测试。
(我没有现成的NVR设备,所以只能使用ONVIF测试工具软件来验证ONVIF了。)

下图是已经集成到 xdisp_virt中的ONVIF效果,使用的是 ONVIF Device Manager 工具来显示的。



在新版本的 xdisp_virt 程序中, 对直播这部分做了加强,除了上面提到的移植live555作为RTSP服务端之外。
还增加了在图像中显示当前日期的功能,比如上图的视频中的左上角位置:
显示了当前的日期,时间,精确到毫秒。
同时还显示了当前的图像采集 FPS(帧率),图像传输的实时速度,xdisp_virt程序当前使用的CPU的百分比。

同时还考虑到 对于标准直播协议,RTSP,RTMP等,需要连续不断的传输视频流,
否则可能会出现无视频信号等各类错误。而xdisp_virt程序本身则是根据屏幕的变化情况来决定是否传输屏幕数据。
因此在电脑屏幕长期无变化的情况下,xdisp_virt 程序不会传输任何视频数据,
这会造成RTSP,RTMP这些标准协议可能因为无视频信号而中断传输。
为了解决这个问题,xdisp_virt新版本增加了屏幕无变化也传输的功能,具体设置可以查看下图所示:
   其中视频图像添加日期和时间戳,就是在远端图像中添加时间戳,实现方式就跟水印一样。



同时还增加了录制本地视频文件保存到被控机器上,这个功能以前的版本也存在,只是以前的只能录制单个的视频文件,
而这次增加的是把视频文件按照时间段分成多个零散的视频文件,保存到目录中,如下图所示:




有兴趣可以关注发布到 github上的新版本 xdisp_virt 程序。
GitHub - fanxiushu/xdisp_virt: xfsredir file system

 

 

 

以上是关于Hikvison对接NVR实现WEB无插件开发包实现前端视频预览(htmlvuenginx代理)的主要内容,如果未能解决你的问题,请参考以下文章

EasyStreamClient对接海康流媒体V4.X实现无插件播放流程记录

web端对接海康视频3.2开发包以及遇到的坑

Windows远程桌面实现之十二:桌面屏幕通过ONVIF协议与NVR等监控录像设备对接,以及进一步增强直播功能

Windows远程桌面实现之十二:桌面屏幕通过ONVIF协议与NVR等监控录像设备对接,以及进一步增强直播功能

Windows远程桌面实现之十二:桌面屏幕通过ONVIF协议与NVR等监控录像设备对接,以及进一步增强直播功能

Windows远程桌面实现之十二:桌面屏幕通过ONVIF协议与NVR等监控录像设备对接,以及进一步增强直播功能