海康威视频监控设备Web查看系统(二):服务器篇

声明:本系列文章只提供交流与学习使用。文章中所有涉及到海康威视设备的SDK均可在海康威视官方网站下载得到。文章中所有除官方SDK意外的代码均可随意使用,任何涉及到海康威视公司利益的非正常使用由使用者自己负责,与本人无关。

前言:

上一篇文章《海康威视频监控设备Web查看系统(一):概要篇》笼统的介绍了关于海康视频中转方案的思路,本文将一步步实现方案中的视频中转服务端。文中会涉及到一些.net socket处理和基础的多线程操作。我用的是SDK版本是SDK_Win32_V4.2.8.1 。大家根据自己实际情况想在相应的SDK,页面的说明里有详细的设备型号列表。

分析官方SDK的Demo:

首先来看看官方SDK中的C#版本的Demo,官方Demo分为两个版本,分别是“实时预览示例代码一”和“实时预览示例代码二”,因为有现成的C#版本,所以我们使用示例代码一中的内容。首先关注名为CHCNetSDK的类,这个类封中装了SDK中的所有非托管方法接口,我们需要来把这个类以及SDK中的DLL文件一起引入到我们的项目中,如果有对C#调用C++类库不了解的朋友请自己Google一下,资料非常多,博客园里也有很多作者写过这一类的文章,本文就不就这个内容做深入讨论。

调用SDK没有问题了,接下来看看SDK的使用,根据SDK使用文档,SDK接口的调用需要通过一个标准流程,流程图如下:

按照这个流程,我们第一步要做的是初始化SDK,然后是三个可选回调函数的设置,接着要做用户注册设备即设备登录,紧接着就是核心的部分了,根据上一篇文章中讲的思路,除了预览模块外其他几个模块的调用不在我们要解决的问题范畴,因此不予考虑。最后一步是注销设备,释放SDK资源。所以,最后根据我们的需求,流程简化如下:

虽然标准流程如此,但是我们的服务端程序只有一个单一的任务,所以也没有必要对为托管资源进行释放,因为如果退出程序以后资源就会释放,不退出程序的话,SDK资源就不应该被释放。因此再简化一下流程每个节点都有相应的代码实现如如下所示:

 1 //初始化SDK
 2 CHCNetSDK.NET_DVR_Init();
 3
 4 //用户登录
 5 CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30();
 6 CHCNetSDK.NET_DVR_Login_V30(设备IP地址, 设备端口, 用户名, 密码, ref DeviceInfo);
 7 //说明:关于设备IP、端口、用户名及密码信息请根据自己要访问设备的设置正确填写
 8
 9 //预览模块
10 CHCNetSDK.NET_DVR_CLIENTINFO lpClientInfo = new CHCNetSDK.NET_DVR_CLIENTINFO();
11 lpClientInfo.lChannel = channel;
12 lpClientInfo.lLinkMode = 0x0000;
13 lpClientInfo.sMultiCastIP = "";
14 m_fRealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack);
15 IntPtr pUser = new IntPtr();
16 CHCNetSDK.NET_DVR_RealPlay_V30(m_lUserID, ref lpClientInfo, m_fRealData, pUser, 1);
17 //说明:这里的NET_DVR_CLIENTINFO类中缺少预览窗口的句柄,需要预览时,要根据自己的项目设置NET_DVR_CLIENTINFO对象的hPlayWnd属性

可能有朋友看到这里已经忍受不了了,说好的视频中转功能在哪呢?别着急,一切的处理都在回调函数RealDataCallBack中,先耐心看一下这个回调函数的签名

void RealDataCallBack(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser)

第一个lRealHandle是预览控件的句柄,第二个参数dwDataType说明回调接收到的数据类型,pBuffer 存放数据的缓冲区指针, dwBufSize 缓冲区大小 ,pUser 用户数据的句柄。我做的这个视频的中转功能其实就是在这个回调函数中实现的。

好了,核心的代码都摘出来了,大家按照SDK提供的Demo照猫画虎就可以把预览功能实现出来了。

服务端设计:

  实现了预览功能,下面看看中转服务的实现。其中包含三个类:Server,Client以及ClientList类。

Server类主要负责从设备读取数据并将数据缓存到服务器上,并且作为Socket监听服务端;ClientList维护一个客户端列表,并在Server获取到设备数据时便利客户端列表发送数据到客户端;Client类主要负责将服务端缓存的数据分发到各个终端请求上。

三个类的关系及主要成员请看下图:

Server类:

  1 class Server
  2     {
  3         int m_lUserID = -1;
  4         //头数据
  5         byte[] headStream;
  6
  7         ClientList clientList = ClientList.GetClientList();
  8         CHCNetSDK.REALDATACALLBACK m_fRealData;
  9         Socket listenSocket;
 10         Semaphore m_maxNumberAcceptedClients;
 11         /// <summary>
 12         /// Server构造函数,启动服务端Socket及海康SDK获取设备数据
 13         /// </summary>
 14         /// <param name="ipPoint">服务端IP配置</param>
 15         /// <param name="numConnections">最大客户端连接数</param>
 16         /// <param name="channel">设备监听通道</param>
 17         public Server(IPEndPoint ipPoint, int numConnections, int channel)
 18         {
 19             if (!InitHK())
 20             {
 21                 return;
 22             }
 23             RunGetStream(channel);
 24
 25             listenSocket = new Socket(ipPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
 26             listenSocket.Bind(ipPoint);
 27             m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
 28             listenSocket.Listen(100);
 29             Console.WriteLine("开始监听客户端连接......");
 30             StartAccept(null);
 31         }
 32
 33         #region HKSDK
 34
 35         private void RunGetStream(int channel)
 36         {
 37             if (m_lUserID != -1)//初始化成功
 38             {
 39                 CHCNetSDK.NET_DVR_CLIENTINFO lpClientInfo = new CHCNetSDK.NET_DVR_CLIENTINFO();
 40                 lpClientInfo.lChannel = channel;
 41                 lpClientInfo.lLinkMode = 0x0000;
 42                 lpClientInfo.sMultiCastIP = "";
 43                 m_fRealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack);
 44                 IntPtr pUser = new IntPtr();
 45                 int m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V30(m_lUserID, ref lpClientInfo, m_fRealData, pUser, 1);
 46                 Console.WriteLine("开始获取视频数据......");
 47             }
 48             else//初始化 失败,因为已经初始化了
 49             {
 50                 Console.WriteLine("视频数据获取失败......");
 51             }
 52         }
 53
 54         private bool InitHK()
 55         {
 56             bool m_bInitSDK = CHCNetSDK.NET_DVR_Init();
 57             if (m_bInitSDK == false)
 58             {
 59                 return false;
 60             }
 61             else
 62             {
 63                 Console.WriteLine("设备SDK初始化成功.......");
 64                 CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30();
 65                 m_lUserID = CHCNetSDK.NET_DVR_Login_V30("设备IP", 连接端口, "连接用户名", "连接密码", ref DeviceInfo);
 66                 if (m_lUserID != -1)
 67                 {
 68                     Console.WriteLine("监控设备登录成功.......");
 69                     return true;
 70                 }
 71                 else
 72                 {
 73                     Console.WriteLine("监控设备登录失败,稍后再试.......");
 74                     return false;
 75                 }
 76             }
 77         }
 78
 79         private void RealDataCallBack(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser)
 80         {
 81             byte[] data = new byte[dwBufSize];
 82             Marshal.Copy(pBuffer, data, 0, (int)dwBufSize);
 83             Console.WriteLine("监控设备连接正常......");
 84             if (dwDataType == CHCNetSDK.NET_DVR_SYSHEAD)
 85             {
 86                 headStream = data;
 87             }
 88             clientList.SetSendData(data);
 89             return;
 90         }
 91
 92         #endregion
 93
 94         #region Socket
 95         /// <summary>
 96         /// 监听客户端
 97         /// </summary>
 98         /// <param name="acceptEventArg"></param>
 99         private void StartAccept(SocketAsyncEventArgs acceptEventArg)
100         {
101             if (acceptEventArg == null)
102             {
103                 acceptEventArg = new SocketAsyncEventArgs();
104                 acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
105             }
106             else
107             {
108                 acceptEventArg.AcceptSocket = null;
109             }
110
111             m_maxNumberAcceptedClients.WaitOne();
112             bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
113             if (!willRaiseEvent)
114             {
115                 ProcessAccept(acceptEventArg);
116             }
117         }
118         /// <summary>
119         /// 增加客户端列表
120         /// </summary>
121         /// <param name="e"></param>
122         private void ProcessAccept(SocketAsyncEventArgs e)
123         {
124             clientList.AddClient(new Client(e.AcceptSocket, headStream));
125             StartAccept(e);
126         }
127
128         /// <summary>
129         /// Socket回调函数
130         /// </summary>
131         /// <param name="sender"></param>
132         /// <param name="e"></param>
133         private void IO_Completed(object sender, SocketAsyncEventArgs e)
134         {
135             switch (e.LastOperation)
136             {
137                 case SocketAsyncOperation.Accept:
138                     ProcessAccept(e);
139                     break;
140                 default:
141                     throw new ArgumentException("The last operation completed on the socket was not a receive or send");
142             }
143         }
144
145         #endregion
146
147     }

ServerClass

这里有个细节问题要说明一下,当服务端每次注册到设备时,设备第一次返回的数据里面的前40个字节是头数据,在解码阶段时需要将这40字节数据先发送给解码程序,否则解码程序将无法正常操作。所以在Server类中单独保存了这40字节的头数据以备分发给各个客户端。

另外,由于我们的客户端只需要不停的从服务端接收数据,所以服务端设计时只需要将数据分发给客户端即可,无需在Server类中维护客户端状态,因此,服务端Socket只进行监听操作,当监听到有客户端连接时,将客户端连接添加到ClientList即可。下面看看ClientList类的实现:

class ClientList
{
    private static ClientList list = null;
    private ClientList() { }
    private List<Client> socketList = new List<Client>();

    /// <summary>
    /// 获取ClientList单例
    /// </summary>
    /// <returns></returns>
    public static ClientList GetClientList()
    {
        if (list == null)
            list = new ClientList();
        return list;
    }
    /// <summary>
    /// 将客户端增加到ClientList中
    /// </summary>
    /// <param name="client"></param>
    public void AddClient(Client client)
    {
        this.socketList.Add(client);
    }
    /// <summary>
    /// 遍历发送数据到客户端
    /// </summary>
    /// <param name="data"></param>
    public void SetSendData(byte[] data)
    {
        socketList.RemoveAll((s) => { return s.SocketError != SocketError.Success; });
        PerformanceCounter p = new PerformanceCounter("Processor", "% Processor Time", "_Total");
        for (int i = 0; i < socketList.Count; i++)
        {
            socketList[i].SetData(data);
            if (p.NextValue() > 50)
                Thread.Sleep(10);
        }
    }
}

ClientListClass

在SetSendData方法中遍历客户端列表发送数据时,用到了PerformanceCounter对象来控制服务器CPU的使用率,防止CPU资源过载。在实际运行过程中需要对PerformanceCounter对象获取的使用率的条件和线程等待时间做适当的微调来达到想要的效果。我这里的参数是我在PC Server上部署的时候采用的,如果是高CPU配置的话,需要把CPU使用率的判断条件改小一些,否则会出现服务端单次从设备读取数据时间过长的问题,在客户端显示时出现延时。

最后看看Client类的实现:

  1 class Client
  2 {
  3     /// <summary>
  4     /// 客户端连接Socket
  5     /// </summary>
  6     private Socket socket;
  7     /// <summary>
  8     /// 发送的数据类型
  9     /// </summary>
 10     private BufferType type = BufferType.Head;
 11     /// <summary>
 12     /// 头数据
 13     /// </summary>
 14     private byte[] headStream;
 15     private SocketError socketError = SocketError.Success;
 16     /// <summary>
 17     /// 控制数据发送顺序信号量
 18     /// </summary>
 19     private ManualResetEvent sendManual = new ManualResetEvent(false);
 20     private byte[] sendData;
 21     /// <summary>
 22     /// 发送数据线程
 23     /// </summary>
 24     private Thread sendThread;
 25     /// <summary>
 26     /// 客户端构造函数
 27     /// </summary>
 28     /// <param name="socket"></param>
 29     /// <param name="headStream"></param>
 30     public Client(Socket socket, byte[] headStream)
 31     {
 32         this.headStream = headStream;
 33         this.socket = socket;
 34         sendThread = new Thread((object arg) =>
 35         {
 36
 37             while (true)
 38             {
 39                 sendManual.WaitOne();
 40                 if (socketError == SocketError.Success)
 41                 {
 42                     try
 43                     {
 44                         Console.WriteLine(sendData.Length);
 45                         socket.Send(sendData);
 46                     }
 47                     catch (Exception)
 48                     {
 49                         Distroy();
 50                         break;
 51                     }
 52
 53                 }
 54                 sendManual.Reset();
 55             }
 56         });
 57         sendThread.IsBackground = true;
 58         sendThread.Start();
 59     }
 60     /// <summary>
 61     ///
 62     /// </summary>
 63     public SocketError SocketError
 64     {
 65         get
 66         {
 67             return socketError;
 68         }
 69     }
 70     /// <summary>
 71     ///
 72     /// </summary>
 73     /// <param name="data"></param>
 74     public void SetData(byte[] data)
 75     {
 76         if (this.socketError != SocketError.Success)
 77         {
 78             return;
 79         }
 80         if (type == BufferType.Head && headStream.Length == 40)
 81         {
 82             sendData = headStream;
 83             type = BufferType.Body;
 84         }
 85         else
 86         {
 87             sendData = data;
 88         }
 89         sendManual.Set();
 90     }
 91     /// <summary>
 92     /// 销毁Client对象,释放资源
 93     /// </summary>
 94     private void Distroy()
 95     {
 96         this.sendThread.Abort();
 97         this.socket.Shutdown(SocketShutdown.Both);
 98         this.socket.Dispose();
 99         this.socketError = SocketError.ConnectionRefused;
100     }
101 }
102
103 enum BufferType
104 {
105     Head, Body
106 }

ClientClass

简要说明一下,因为中转服务的一直处于大量连接数据的发送过程中,所以在Client的构造函数中为每一个实例开了一个本地线程作为数据发送的处理线程,而不是使用线程池来做处理。另外,使用ManualResetEvent实例作为信号量来控制Client实例在发送数据时是按照Server实例从设备采集的数据的顺序来一条一条发送的,这样避免了由于数据流混乱造成的客户端解码时出现解码错误或者跳帧等现象。

好了,视频中转服务器端的程序已经开发出来了,接下来要做的就是做一个Web插件来接收服务端的数据并解码播放,这些内容留作下一篇内容。敬请关注!

海康威视频监控设备Web查看系统(二):服务器篇,布布扣,bubuko.com

时间: 2024-10-16 19:46:55

海康威视频监控设备Web查看系统(二):服务器篇的相关文章

海康威视频监控设备Web查看系统(三):Web篇

声明:本系列文章只提供交流与学习使用.文章中所有涉及到海康威视设备的SDK均可在海康威视官方网站下载得到.文章中所有除官方SDK以为的代码均可随意使用,任何涉及到海康威视公司利益的非正常使用由使用者自己负责,与本人无关. 前言: <海康威视频监控设备Web查看系统(一):概要篇> <海康威视频监控设备Web查看系统(二):服务器篇> 本文是本系列三篇文章中的最后一篇,在前两篇文章中,介绍了开发海康监控的方案及中转服务器的实现,本篇文章介绍Web端的功能实现,经过本篇文章中的项目开发

海康威视频监控设备Web查看系统(一):概要篇

声明:本系列文章只提供交流与学习使用.文章中所有涉及到海康威视设备的SDK均可在海康威视官方网站下载得到.文章中所有除官方SDK意外的代码均可随意使用,任何涉及到海康威视公司利益的非正常使用由使用者自己负责,与本人无关. 题外话: 为什么在开始之前先说题外话呢?主要是为了怕有人误会,以为这里要写的是一个关于视频流处理的文章.其实这个系列的几篇文章可能和视频流的处理半毛钱关系都没有,冲着视频技术来的看官们,可能让你们失望了.这个系列里主要涉及的技术大概有.net的socket处理,C#写Activ

海康视频监控---Demo

1,使用在页面中调用ActiveX控件 <object classid='clsid:E7EF736D-B4E6-4A5A-BA94-732D71107808' codebase='' standby='Waiting...' id='PreviewActiveX' width='100%' height='100%' name='ocx' align='center'> <param name='wndtype' value='1'> <param name='playmo

海思Hi3518Ev200 4G wifi无线网络视频监控摄像开发板可二次开发

21世纪,经济社会及物联网,云计算,社会网络,车联网等新型移动通信业务日新月异的发展,2013年,我们的通信技术进入了第四代的移动信息系统--4G通信技术.网线下的网络已不能满足需求,人们对移动通信网络,无线通信网络技术的需求已经被应用到各个行业领域.其中4G网络的无线网络视频监控就被各个行业所应用,如野外环境下的监控.渔塘.果园.无人售货柜.无人配送等等. 为了便于产品研发,减少成本.BOJINGnet基于海思HI3518EV200的H264视频压缩SOC研发出了用于4G网络无线网络视频监控摄

【云快讯】《选择视频监控云11个理由》

2015-08-28 张晓东 东方云洞察 点击上面的链接文字,可以快速关注"东方云洞察"公众号 VSaaS(视频监控作为一种服务),是指基于云托管的视频监控.该服务通常包括视频录制.存储.远程查看.管理警报.网络安全等内容.据统计,百分之93的企业已经采用了云解决方案.云计算技术的进步和更大的带宽使得VSaaS(也被称为云视频监控)越来越有吸引力. 本文节选自Dean Drako的白皮书<11 reasons Why VideoSurveillance is Moving to

一周集成行业智能监控应用,阿里云发布智能视频监控平台

在4月22-25日于上海举办的2019联通合作伙伴大会上,阿里云首次对外发布了智能视频监控平台,同时向参会的数千名伙伴及业界人士演示了一分钟视频监控上云系统,阐述了阿里云智能视频监控平台助力传统监控领域上云的优势和方法. 在视频监控领域,上云和AI是未来的趋势,阿里云智能视频监控解决方案无缝集成了视频监控产品和智能视觉产品.该平台依托遍布全球的边缘接入节点和出色的视频技术,面向监控设备提供统一开放的视频流接入.处理和分发服务.将传统的本地监控视频内容接入云端,进行存储.录制回看.全网分发,同时通

VSAM:视频监控系统 A System for Video Surveillance and Monitoring

VSAM(VideoSurveillance and Monitoring)视频监控系统 Robotics Institute CMU 1:引言 2:试验床介绍 3:基本的视频分析算法:运动目标检测,跟踪,分类,简单行为识别 4:地理空间模型,感知网络标定,三维地理位置估计,目标图形显示 5:多摄像机协作 6:成果展示及未来的研究计划 1 引言 VSAM可自动解析场景中的人和车,检测目标并按语义分类,如人.人群.车,以及在此基础上的行为分析,如走动.奔跑.利用VSAM,单个人即可监控复杂区域,跟

Qt编写安防视频监控系统18-云台控制

一.前言 云台控制是视频监控系统中必备的一个功能,对球机进行上下左右的移动,还有焦距的控制,其实核心就是控制XYZ三个坐标轴,为了开发这个模块,特意研究了各种云台控制的方法和开源库比如soap,有些厂家使用自家SDK控制云台,但是大部分都会选择onvif来控制,毕竟是国际标准的通用的,只要符合这个标准的都可以使用,onvif协议的解析通常用的开源库是soap,涵盖的内容比较全,包括获取各种设备信息和回控等,缺点就是比较臃肿,使用非常不容易,函数名实在是有点不顺手,很多新手都绕在其中不知所措最后放

高清网络视频监控系统中交换机的选择

目前视频监控系统已经完成从传统模拟视频监控到数字视频监控的全面升级,并逐渐发展到高清网络数字视频监控阶段,利用现有的办公网络.行业专网.光纤专网,甚至互联网和无线网络等基础架构,搭建以区域安防.生产监视等不同目的的高清网络视频监控系统.其中百万级像素网络摄像机的大规模普及,既解决了传统模拟视频监控系统清晰度不足的尴尬局面也提高了监控系统的便捷性和安全性. 高清网络视频监控系统优势 先进性:利用现有的综合布线网络传输图像,进行实时监控系统所需的前端设备少,连线简洁,后端仅需一套软件系统即可; 可靠