C#实现海康威SDK视监控预览
前些日子老板要在公司的大屏幕呈现海康威视的监控,并且要求三个九宫格,显示不同的通道(摄像头)。
由于要得比较急,我的第一个方案是采用JS控制网页,自动点击对应的通道,但很显然不行。
尽管是使用网页主控端呈现,但是海康需要安装个客户端插件。
我们在网页中呈现的时候,其实就是给客户端插件发送WebSocket消息,让它显示对应的通道,然后客户端插件就会在网页的位置显示对应的画面,本质上还是用了客户端来播放。
仅仅用JS控制网页是不行的。
于是我结合JS脚本和Api函数SendInput来实现,而且还需要以管理员身份去运行程序,才可以正常调用SendInput点击客户端的窗口。
这个方案有点Low,效果不太理想,因为点击需要间隔,速度慢,而且还会出现点击错漏、错位等问题。
后来我仔细研究了一番,发现可以通过网页主控端开放SDK接口来直接获取监控画面,而且不需要安装客户端。
下面就介绍一下具体的实现方法。
1. 准备工作
首先,你需要准备好以下工具:
- Visual Studio 2019
- 海康威视设备集成SDK
2. 下载海康威视设备集成SDK
海康开放平台:https://open.hikvision.com/
在这之前,需要注册一个海康威视的账号才能下载。

点开【开放体系】菜单,点击【设备集成SDK】。

点击【资源下载】。

选择【设备网络SDK】,并下载【设备网络SDK_Win64 V6.1.9.48_build20230410】。

下载完成后,解压到任意目录,进入【Demo示例】。

进入【3- csharp 开发示例】。

打开【1-实时预览示例代码一】项目。

这个项目重要的文件就是【CHCNetSDK.cs】,这个已经声明好模块的接口,我们只需要调用就可以了。

打开海康威视的网页主控端,启用SDK功能。

测试一下是否可以点亮。

成功!
接下来只需要把CHCNetSDK.cs复制到自己的项目,进行开发一下(CV大法)就好了。

具体的实现可以参考我的代码。
3. 代码实现
- 可以先参考海康威视的开发文档。
构造函数
调用初始化函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public FrmMain()
{
InitializeComponent();
var m_bInitSDK = CHCNetSDK.NET_DVR_Init();
if (m_bInitSDK == false)
{
MessageBox.Show("NET_DVR_Init error!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
//保存SDK日志 To save the SDK log
CHCNetSDK.NET_DVR_SetLogToFile(1, $@"{Application.StartupPath}\SdkLog\", true);
}
}
通道信息类
预览成功后,保存用户标识、通道信息。
1
2
3
4
5
6
public class HikVisionChannel
{
public int UserID { get; set; } // 登录得到的标识
public int Channel { get; set; } // 预览的通道号(摄像头)
public int RealHandle { get; set; } // 预览得到的句柄
}
声明全局变量
登录的时候由于三个九宫格中的两个是同一个地址,可以用同一个账号,所以两个账号只需要登录一次,即相同的账号不需登录两次。
UserIDs:保存登录得到的用户标识、HikChannels:保存通道信息、LoginCallBack:登录回调函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <summary>
/// 保存登录得到的用户标识
/// </summary>
private readonly ConcurrentDictionary<string, int> UserIDs = new ConcurrentDictionary<string, int>();
/// <summary>
/// 保存通道信息
/// </summary>
private readonly ConcurrentDictionary<string, HikVisionChannel> HikChannels = new ConcurrentDictionary<string, HikVisionChannel>();
/// <summary>
/// 登录回调函数
/// </summary>
private CHCNetSDK.LOGINRESULTCALLBACK LoginCallBack = null;
登录函数
如果已经登录过,则直接返回用户标识。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public int Login(string ip, int port, string username, string password)
{
if (string.IsNullOrWhiteSpace(ip) || port == 0 ||
string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
MessageBox.Show("请输入正确的IP、端口、用户名和密码!");
return -1;
}
//以设备IP地址和用户名作为键,如果已经登录过,则直接返回用户标识
string userKey = ip + username;
if (UserIDs.ContainsKey(userKey))
{
return UserIDs[userKey];
}
var struLogInfo = new CHCNetSDK.NET_DVR_USER_LOGIN_INFO();
//设备IP地址或者域名
byte[] byIP = System.Text.Encoding.Default.GetBytes(ip);
struLogInfo.sDeviceAddress = new byte[129];
byIP.CopyTo(struLogInfo.sDeviceAddress, 0);
//设备用户名
byte[] byUserName = System.Text.Encoding.Default.GetBytes(username);
struLogInfo.sUserName = new byte[64];
byUserName.CopyTo(struLogInfo.sUserName, 0);
//设备密码
byte[] byPassword = System.Text.Encoding.Default.GetBytes(password);
struLogInfo.sPassword = new byte[64];
byPassword.CopyTo(struLogInfo.sPassword, 0);
struLogInfo.wPort = (ushort)port;//设备服务端口号
if (LoginCallBack == null)
{
LoginCallBack = new CHCNetSDK.LOGINRESULTCALLBACK(cbLoginCallBack);//注册回调函数
}
struLogInfo.cbLoginResult = LoginCallBack;
struLogInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是
var DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V40();
//登录设备
var m_lUserID = CHCNetSDK.NET_DVR_Login_V40(ref struLogInfo, ref DeviceInfo);
if (m_lUserID < 0)
{
var iLastErr = CHCNetSDK.NET_DVR_GetLastError();
var str = "NET_DVR_Login_V40 failed, error code= " + iLastErr; //登录失败,输出错误号
LogHelper.Default.NG(str); // 记录日志,LogHelper是我自己写的日志记录类,你们可以自己实现。
return -1;
}
else
{
//登录成功,登录后的用户标识保存到字典中
UserIDs[userKey] = m_lUserID;
GC.KeepAlive(UserIDs);
return m_lUserID;
}
}
public void cbLoginCallBack(int lUserID, int dwResult, IntPtr lpDeviceInfo, IntPtr pUser)
{
string strLoginCallBack = "登录设备,lUserID:" + lUserID + ",dwResult:" + dwResult;
if (dwResult == 0)
{
uint iErrCode = CHCNetSDK.NET_DVR_GetLastError();
strLoginCallBack = strLoginCallBack + ",错误号:" + iErrCode;
LogHelper.Default.NG(strLoginCallBack);
}
else
{
LogHelper.Default.OK(strLoginCallBack);
}
}
预览指定的通道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public bool PreviewChannel(Control control, int userID, int channel)
{
CHCNetSDK.NET_DVR_PREVIEWINFO lpPreviewInfo = new CHCNetSDK.NET_DVR_PREVIEWINFO();
lpPreviewInfo.hPlayWnd = control.Handle;//预览窗口
lpPreviewInfo.lChannel = channel;//预览的设备通道
lpPreviewInfo.dwStreamType = 0;//码流类型:0-主码流,1-子码流,2-码流3,3-码流4,以此类推
lpPreviewInfo.dwLinkMode = 0;//连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP
lpPreviewInfo.bBlocked = true; //0- 非阻塞取流,1- 阻塞取流
lpPreviewInfo.dwDisplayBufNum = 1; //播放库播放缓冲区最大缓冲帧数
lpPreviewInfo.byProtoType = 0;
lpPreviewInfo.byPreviewMode = 0;
//如果需要保存直播数据,可以设置RealDataCallBack回调函数
//if (RealData == null)
//{
// RealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack);//预览实时流回调函数
//}
IntPtr pUser = new IntPtr();//用户数据
//打开预览
var m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V40(userID, ref lpPreviewInfo, null, pUser);
if (m_lRealHandle < 0)
{
var iLastErr = CHCNetSDK.NET_DVR_GetLastError();
var str = "NET_DVR_RealPlay_V40 failed, error code= " + iLastErr; //预览失败,输出错误号
LogHelper.Default.NG(str);
return false;
}
else
{
//预览成功,以控件名称为键,保存预览句柄到字典中
HikChannels[control.Name] = new HikVisionChannel
{
UserID = userID,
Channel = channel,
RealHandle = m_lRealHandle
};
GC.KeepAlive(HikChannels);
return true;
}
}
停止预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void StopPreviewChannel(string controlName)
{
if (HikChannels.ContainsKey(controlName))
{
int realHandle = HikChannels[controlName].RealHandle;
if (!CHCNetSDK.NET_DVR_StopRealPlay(realHandle))
{
var iLastErr = CHCNetSDK.NET_DVR_GetLastError();
var str = "NET_DVR_StopRealPlay failed, error code= " + iLastErr;
LogHelper.Default.NG(str);
return;
}
HikChannels.TryRemove(controlName, out HikVisionChannel tmp); // 移除通道
}
}
界面上放3个九宫格
可以用TableLayoutPanel来布局,三个TableLayoutPanel,每个TableLayoutPanel放9个PictureBox控件。
- 
预览到一个表格面板
TableLayoutPanel下面的所有PictureBox。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void PreviewToPanel(TableLayoutPanel tableLayoutPanel, int userID, List<int> channelList)
{
var controls = tableLayoutPanel.Controls.Cast<Control>()
.Where(v => v is PictureBox).OrderBy(v => int.Parse(v.Name.Replace("pictureBox", "")));
if (controls.Count() == channelList.Length)
{
int n = 0;
foreach (var item in controls)
{
PreviewChannel(item, userID, channelList[n]);
n++;
}
}
}
在Load事件中
先登录,然后预览到三个表格面板
1
2
3
4
5
6
7
8
9
10
11
private void FrmMain_Load(object sender, EventArgs e)
{
//登录
int userID1 = Login("192.168.1.101", 8000, "admin", "123456");
int userID2 = Login("192.168.1.100", 8000, "admin", "123456");
int userID3 = Login("192.168.1.100", 8000, "admin", "123456");
//预览
PreviewToPanel(tableLayoutPanel1, userID1, new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
PreviewToPanel(tableLayoutPanel2, userID2, new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
PreviewToPanel(tableLayoutPanel3, userID3, new List<int> { 10, 11, 12, 13, 14, 15, 16, 17, 18 });
}
关闭窗口之前
停止预览、登出设备、清理资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
//停止预览
foreach (var item in HikChannels)
{
StopPreviewChannel(item.Key);
}
//登出设备
foreach (var item in UserIDs)
{
CHCNetSDK.NET_DVR_Logout(item.Value);
}
//清理资源
CHCNetSDK.NET_DVR_Cleanup();
}
最后看一下效果
打了马赛克
- 
4. 总结
通过海康威视设备集成SDK,可以实现海康威视监控预览,不需要安装海康威视客户端。
海康威视提供了代码例子,相对来说还是比较容易上手的。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 阿枫的客栈!
评论



