声明:本系列文章只提供交流与学习使用。文章中所有涉及到海康威视设备的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资源就不应该被释放。因此再简化一下流程每个节点都有相应的代码实现如如下所示:

 //初始化SDK
CHCNetSDK.NET_DVR_Init(); //用户登录
CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30();
CHCNetSDK.NET_DVR_Login_V30(设备IP地址, 设备端口, 用户名, 密码, ref DeviceInfo);
//说明:关于设备IP、端口、用户名及密码信息请根据自己要访问设备的设置正确填写 //预览模块
CHCNetSDK.NET_DVR_CLIENTINFO lpClientInfo = new CHCNetSDK.NET_DVR_CLIENTINFO();
lpClientInfo.lChannel = channel;
lpClientInfo.lLinkMode = 0x0000;
lpClientInfo.sMultiCastIP = "";
m_fRealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack);
IntPtr pUser = new IntPtr();
CHCNetSDK.NET_DVR_RealPlay_V30(m_lUserID, ref lpClientInfo, m_fRealData, pUser, );
//说明:这里的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类:

 class Server
{
int m_lUserID = -;
//头数据
byte[] headStream; ClientList clientList = ClientList.GetClientList();
CHCNetSDK.REALDATACALLBACK m_fRealData;
Socket listenSocket;
Semaphore m_maxNumberAcceptedClients;
/// <summary>
/// Server构造函数,启动服务端Socket及海康SDK获取设备数据
/// </summary>
/// <param name="ipPoint">服务端IP配置</param>
/// <param name="numConnections">最大客户端连接数</param>
/// <param name="channel">设备监听通道</param>
public Server(IPEndPoint ipPoint, int numConnections, int channel)
{
if (!InitHK())
{
return;
}
RunGetStream(channel); listenSocket = new Socket(ipPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(ipPoint);
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
listenSocket.Listen();
Console.WriteLine("开始监听客户端连接......");
StartAccept(null);
} #region HKSDK private void RunGetStream(int channel)
{
if (m_lUserID != -)//初始化成功
{
CHCNetSDK.NET_DVR_CLIENTINFO lpClientInfo = new CHCNetSDK.NET_DVR_CLIENTINFO();
lpClientInfo.lChannel = channel;
lpClientInfo.lLinkMode = 0x0000;
lpClientInfo.sMultiCastIP = "";
m_fRealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack);
IntPtr pUser = new IntPtr();
int m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V30(m_lUserID, ref lpClientInfo, m_fRealData, pUser, );
Console.WriteLine("开始获取视频数据......");
}
else//初始化 失败,因为已经初始化了
{
Console.WriteLine("视频数据获取失败......");
}
} private bool InitHK()
{
bool m_bInitSDK = CHCNetSDK.NET_DVR_Init();
if (m_bInitSDK == false)
{
return false;
}
else
{
Console.WriteLine("设备SDK初始化成功.......");
CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30();
m_lUserID = CHCNetSDK.NET_DVR_Login_V30("设备IP", 连接端口, "连接用户名", "连接密码", ref DeviceInfo);
if (m_lUserID != -)
{
Console.WriteLine("监控设备登录成功.......");
return true;
}
else
{
Console.WriteLine("监控设备登录失败,稍后再试.......");
return false;
}
}
} private void RealDataCallBack(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser)
{
byte[] data = new byte[dwBufSize];
Marshal.Copy(pBuffer, data, , (int)dwBufSize);
Console.WriteLine("监控设备连接正常......");
if (dwDataType == CHCNetSDK.NET_DVR_SYSHEAD)
{
headStream = data;
}
clientList.SetSendData(data);
return;
} #endregion #region Socket
/// <summary>
/// 监听客户端
/// </summary>
/// <param name="acceptEventArg"></param>
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
}
else
{
acceptEventArg.AcceptSocket = null;
} m_maxNumberAcceptedClients.WaitOne();
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
}
/// <summary>
/// 增加客户端列表
/// </summary>
/// <param name="e"></param>
private void ProcessAccept(SocketAsyncEventArgs e)
{
clientList.AddClient(new Client(e.AcceptSocket, headStream));
StartAccept(e);
} /// <summary>
/// Socket回调函数
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void IO_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Accept:
ProcessAccept(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
} #endregion }

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 = ; i < socketList.Count; i++)
{
socketList[i].SetData(data);
if (p.NextValue() > )
Thread.Sleep();
}
}
}

ClientListClass

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

最后看看Client类的实现:

 class Client
{
/// <summary>
/// 客户端连接Socket
/// </summary>
private Socket socket;
/// <summary>
/// 发送的数据类型
/// </summary>
private BufferType type = BufferType.Head;
/// <summary>
/// 头数据
/// </summary>
private byte[] headStream;
private SocketError socketError = SocketError.Success;
/// <summary>
/// 控制数据发送顺序信号量
/// </summary>
private ManualResetEvent sendManual = new ManualResetEvent(false);
private byte[] sendData;
/// <summary>
/// 发送数据线程
/// </summary>
private Thread sendThread;
/// <summary>
/// 客户端构造函数
/// </summary>
/// <param name="socket"></param>
/// <param name="headStream"></param>
public Client(Socket socket, byte[] headStream)
{
this.headStream = headStream;
this.socket = socket;
sendThread = new Thread((object arg) =>
{ while (true)
{
sendManual.WaitOne();
if (socketError == SocketError.Success)
{
try
{
Console.WriteLine(sendData.Length);
socket.Send(sendData);
}
catch (Exception)
{
Distroy();
break;
} }
sendManual.Reset();
}
});
sendThread.IsBackground = true;
sendThread.Start();
}
/// <summary>
///
/// </summary>
public SocketError SocketError
{
get
{
return socketError;
}
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
public void SetData(byte[] data)
{
if (this.socketError != SocketError.Success)
{
return;
}
if (type == BufferType.Head && headStream.Length == )
{
sendData = headStream;
type = BufferType.Body;
}
else
{
sendData = data;
}
sendManual.Set();
}
/// <summary>
/// 销毁Client对象,释放资源
/// </summary>
private void Distroy()
{
this.sendThread.Abort();
this.socket.Shutdown(SocketShutdown.Both);
this.socket.Dispose();
this.socketError = SocketError.ConnectionRefused;
}
} enum BufferType
{
Head, Body
}

ClientClass

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

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

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

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

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

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

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

  3. 海康视频监控---Demo

    1,使用在页面中调用ActiveX控件 <object classid='clsid:E7EF736D-B4E6-4A5A-BA94-732D71107808' codebase='' stan ...

  4. C# 视频监控系列:学习地址汇总

    原文地址:http://www.cnblogs.com/over140/archive/2009/04/07/1429308.html 前言 对于视频监控系统大家应该是不陌生的,实施的路况信息.地铁. ...

  5. 视频监控——从其他浏览器打开低版本IE方案

    1. 方案背景 由于低版本IE浏览器并不支持很多新的页面技术,导致部分页面效果难以实现;另一方面IE浏览器版本与操作系统绑定,难以统一,不同版本IE间的不兼容导致多种兼容性问题,因此本项目暂定采用Ch ...

  6. 【转】C# 视频监控系列(12):H264播放器——播放录像文件

    原文地址:http://www.cnblogs.com/over140/archive/2009/03/23/1419643.html?spm=5176.100239.blogcont51182.16 ...

  7. 【转】C# 视频监控系列(13):H264播放器——控制播放和截图

    本文原文地址:http://www.cnblogs.com/over140/archive/2009/03/30/1421531.html 阿里云栖社区也有相关的视频开发案例:https://yq.a ...

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

    VSAM(VideoSurveillance and Monitoring)视频监控系统 Robotics Institute CMU 1:引言 2:试验床介绍 3:基本的视频分析算法:运动目标检测, ...

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

    一.前言 云台控制是视频监控系统中必备的一个功能,对球机进行上下左右的移动,还有焦距的控制,其实核心就是控制XYZ三个坐标轴,为了开发这个模块,特意研究了各种云台控制的方法和开源库比如soap,有些厂 ...

随机推荐

  1. Mina、Netty、Twisted一起学习(三):TCP前缀固定大小的消息(Header)

    于以前的博文于,有介绍切割消息换行的方法. 但是有一个小问题,这样的方法,设消息中本身就包括换行符,那将会将这条消息切割成两条.结果就不正确了. 本文介绍第二种消息切割方式,即上一篇博文中讲的第2条: ...

  2. Delphi 禁用x64位系统文件重定向功能

    在X64系统里面,一些特殊的目录和特殊的注册表键被分为2个独立的部分.对于文件系统来说,      %systemroot%\system32 目录被保留给64位文件使用,而32位文件会被重定向到%s ...

  3. 解析Android的 消息传递机制Handler

    1. 什么是Handler: Handler 网络释义"机械手.经理"意思,在Android它用于管理多个线程UI操作: 2. 为什么会出现Handler: 在Android里面的 ...

  4. ATS项目更新(1) CC视图与备份路径同步

    1: subst t: /d 2: subst t: D:\PublicViews\Automation_Framework\SQA_ATE_DEV 3: 4: rem ** update folde ...

  5. CORSFilter

    import java.io.IOException; import javax.servlet.Filter;import javax.servlet.FilterChain;import java ...

  6. 如何加入该网站for Linux(绑定域名)

    [路径跟踪配置由阿里云提供的标准环境的路径为准,假设你单独安装.请根据实际的安装路径配置].   1.cd /alidata/server/httpd/conf/vhosts/ 进入绑定域名所在文件夹 ...

  7. WPF元素绑定

    原文:WPF元素绑定 数据绑定简介:数据绑定是一种关系,该关系告诉WPF从源对象提取一些信息,并用这些信息设置目标对象的属性.目标属性是依赖项属性.源对象可以是任何内容,从另一个WPF元素乃至ADO. ...

  8. C# 桌面软件开发-深入学习 [1]- AY-C#人爱学不学-aaronyang技术分享

    原文:C# 桌面软件开发-深入学习 [1]- AY-C#人爱学不学-aaronyang技术分享 曾经我做office,不想依赖别人dll,就使用了 Type.GetTypeFromProgID 可以根 ...

  9. 加快QT工程编译速度(还可给Qt for Android设置)

    一.多核编译 环境:win10, Qt 5.4.1,编译器mingw32 项目: Qt for Android Qt Creator 在编译android项目时不支持预编译,默认cpu单核编译,工程稍 ...

  10. 赵伟国:陆资无法进入台湾紫光要到WTO控告(芯片是为了经济安全,高通找的人不是很聪明)

    集微网消息,昨天由全球半导体联盟和上海市集成电路行业协会联合举办的Memory +论坛在上海举行,会议透过来自存储器.逻辑和系统市场领先企业的高管,深入他们对未来存储器的应用.可行的商业模式,以及逻辑 ...