海康摄像头截图实现

海康摄像头通过实时预览从视频流获取yv12帧数据后通过opencv转存jpeg格式数据。

SDK下载

SDK下载地址,打开网址选择对应开发平台的设备网络sdk和播放库sdk,播放库主要用于解码实时视频流获取每帧yv12数据。

设备网络SDK下载

选择window平台64位版本的sdk,当前版本(SDK_Win64 V6.1.9.4_build20220412)点击下载。

  • 概述
    设备网络SDK是基于设备私有网络通信协议开发的,为嵌入式网络硬盘录像机、NVR、网络摄像机、网络球机、视频服务器、解码器、报警主机、网络存储等产品服务的配套模块,用于远程访问和控制设备软件的二次开发。

  • 设备网络SDK主要功能
    图像预览, 文件回放和下载, 云台控制, 布防/撤防, 语音对讲, 日志管理, 解码卡, 远程升级, 远程重启/关闭, 格式化硬盘, 参数配置(系统配置, 通道配置, 串口配置, 报警配置, 用户配置), 多路解码器, 智能设备功能和获取设备能力集等。

播放库SDK下载

选择window平台64位版本的sdk,当前版本(SDK_Win64 V7.3.9.50_build20210106)点击下载。

  • 概述
    播放库SDK是我司网络硬盘录像机、视频服务器、IP设备等产品播放相关的二次开发包,适用于各系列编码产品录像文件和数据流的解码与播放。

  • 播放库SDK主要功能
    主要用于实时码流预览、录像文件回放、播放控制(如:暂停、单帧前进、单帧后退)、获取码流基本信息(如:创建文件索引、解码帧信息、分辨率、帧率)、支持JPG和BMP两种格式的播放截图等。

代码

初始化设备连接

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
int main()
{
char deviceIp[16] = "172.17.18.233";
char username[16] = "admin";
char password[16] = "Effort@2022";
char libPath[256] = ".\\lib";

auto logger = spdlog::daily_logger_mt("daily_logger", "../logs/play.log", 2, 30);
spdlog::set_pattern("[%Y-%m-%d %T][thread %t][%l]%v");
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug);
spdlog::flush_on(spdlog::level::info);
spdlog::flush_every(std::chrono::seconds(1));

NET_DVR_DEVICEINFO_V40 deviceInfoTmp;
memset(&deviceInfoTmp, 0, sizeof(NET_DVR_DEVICEINFO_V30));

if (!NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_SDK_PATH, libPath)) {
DWORD errCode = NET_DVR_GetLastError();
spdlog::error("设置海康sdk路径失败, code={}, message={}", errCode, NET_DVR_GetErrorMsg((LONG*)&errCode));
return FALSE;
}

if (!NET_DVR_Init()) {
DWORD errCode = NET_DVR_GetLastError();
spdlog::error("初始化海康dvr环境失败, code={}, message={}", errCode, NET_DVR_GetErrorMsg((LONG*)&errCode));
return FALSE;
}

//设置连接超时时间
NET_DVR_SetConnectTime(3000, 5);

//设置设备异常处理函数
NET_DVR_SetExceptionCallBack_V30(WM_NULL, NULL, g_ExceptionCallBack, NULL);
NET_DVR_USER_LOGIN_INFO struLoginInfo = { 0 };
struLoginInfo.wPort = 8000;
strcpy_s(struLoginInfo.sUserName, username);
strcpy_s(struLoginInfo.sPassword, password);
strcpy_s(struLoginInfo.sDeviceAddress, deviceIp);

LONG lLoginID = NET_DVR_Login_V40(&struLoginInfo, &deviceInfoTmp);

if (lLoginID == -1)
{
DWORD errCode = NET_DVR_GetLastError();
spdlog::error("连接nvr设备失败, code={}, message={}", errCode, NET_DVR_GetErrorMsg((LONG*)&errCode));
NET_DVR_Cleanup();
return FALSE;
}

std::cout << "loginId:" << lLoginID << std::endl;

NET_DVR_JPEGPARA jpegParam;
jpegParam.wPicQuality = 0;
jpegParam.wPicSize = 0;

// 获取控制台窗口句柄
HMODULE hKernel32 = GetModuleHandle(L"kernel32");
GetConsoleWindowAPI = (PROCGETCONSOLEWINDOW)GetProcAddress(hKernel32, "GetConsoleWindow");
#if 1
//预览参数结构体
NET_DVR_PREVIEWINFO previewInfo = { 0 };
previewInfo.lChannel = 1; //通道号
previewInfo.dwStreamType = 0; //码流类型:0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
previewInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
previewInfo.hPlayWnd = NULL; //播放窗口的句柄,为NULL表示不解码显示。
//previewInfo.hPlayWnd = GetConsoleWindowAPI(); //实时播放视频

previewInfo.bBlocked = 1; //0- 非阻塞取流,1- 阻塞取流
previewInfo.bPassbackRecord = 0; //是否启用录像回传:0-不启用录像回传,1-启用录像回传
previewInfo.byPreviewMode = 0; //延迟预览模式:0- 正常预览,1- 延迟预览
//previewInfo.byStreamID = ; //流ID,为字母、数字和"_"的组合,lChannel为0xffffffff时启用此参数
previewInfo.byProtoType = 0; //应用层取流协议:0- 私有协议,1- RTSP协议。主子码流支持的取流协议通过登录返回结构参数NET_DVR_DEVICEINFO_V30的byMainProto、bySubProto值得知
previewInfo.byRes1 = 0;
previewInfo.byVideoCodingType = 0; //码流数据编解码类型:0- 通用编码数据,1- 热成像探测器产生的原始数据
previewInfo.dwDisplayBufNum = 6; //播放库播放缓冲区最大缓冲帧数,取值范围:1、6(默认,自适应播放模式)、15,置0时默认为1
previewInfo.byNPQMode = 1; //NPQ模式:0- 直连模式,1-过流媒体模式
previewInfo.byRecvMetaData = false;
previewInfo.byDataType = 0; //数据类型:0-码流数据,1-音频数据
memset(previewInfo.byRes, 0, sizeof(previewInfo.byRes));

//实时预览函数
LONG lRealPlayHandle = NET_DVR_RealPlay_V40(lLoginID, &previewInfo, RealPlayV30Callback, NULL);

if (lRealPlayHandle < 0) {
DWORD errCode = NET_DVR_GetLastError();
spdlog::error("海康设备实时预览启动失败, code={}, message={}", errCode, NET_DVR_GetErrorMsg((LONG*)&errCode));
goto endPoint;
}

Sleep(5000);
NET_DVR_StopRealPlay(lLoginID);
#endif

endPoint:
//释放播放库资源
if (lPort > -1) {
PlayM4_Stop(lPort);
PlayM4_CloseStream(lPort);
PlayM4_FreePort(lPort);
}

NET_DVR_Cleanup();
spdlog::drop_all();
spdlog::shutdown();
std::cout << "Hello World!\n";
}

实时预览回调函数

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
//实时预览回调函数
void CALLBACK RealPlayV30Callback(LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, void* pUser) {
#if 1
HWND hWnd = NULL; //如果不需要显示视频 句柄需要传NULL,这样的话就只解码 不显示视频
#else
HWND hWnd = GetConsoleWindowAPI(); //控制台 窗口句柄
#endif

switch (dwDataType)
{
case NET_DVR_SYSHEAD: //系统头
if (lPort >= 0)
{
break; //该通道取流之前已经获取到句柄,后续接口不需要再调用
}

if (!PlayM4_GetPort(&lPort)) //获取播放库未使用的通道号
{
break;
}
//m_iPort = lPort; //第一次回调的是系统头,将获取的播放库port号赋值给全局port,
//下次回调数据时即使用此port号播放
if (dwBufSize > 0)
{
if (!PlayM4_SetStreamOpenMode(lPort, STREAME_REALTIME)) //设置实时流播放模式
{
break;
}

if (!PlayM4_OpenStream(lPort, pBuffer, dwBufSize, 1024 * 1024)) //打开流接口
{
break;
}

//设置解码回调函数
if (!PlayM4_SetDecCallBack(lPort, DecodeFrameCallback)) {
spdlog::error("设置解码器回调函数DecodeFrameCallback失败.");
break;
}

if (!PlayM4_Play(lPort, hWnd)) //播放开始
{
break;
}
}
break;
case NET_DVR_STREAMDATA: //码流数据
if (dwBufSize > 0 && lPort != -1)
{
if (!PlayM4_InputData(lPort, pBuffer, dwBufSize))
{
break;
}
}
break;
default: //其他数据
if (dwBufSize > 0 && lPort != -1)
{
if (!PlayM4_InputData(lPort, pBuffer, dwBufSize))
{
break;
}
}
break;
}
}

设置解码回调函数

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
///////////////////////////////////////////////////只解码不显示抓图实现方式///////////////////////////////
void CALLBACK DecodeFrameCallback(long nPort, char* pBuffer, long nSize, FRAME_INFO* pFrameInfo, long nReserved1, long nReserved2) {
spdlog::info("播放解码数据回调函数 nSize={},nWidth={},nHeight={},nStamp={},nType={}", nSize, pFrameInfo->nWidth, pFrameInfo->nHeight, pFrameInfo->nStamp, pFrameInfo->nType);

switch (pFrameInfo->nType)
{
case T_RGB32:
//视频数据,yv12格式
break;
case T_UYVY:
//视频数据,uyvy格式
break;
case T_YV12:
//视频数据。每个像素4个字节,排列方式与位图相似,“B-G-R-0 …”,第一个像素位于图像左下角
//保存jpeg图像到磁盘
{
//获取yv12缓存转换成mat对象逻辑
long time1 = clock();
IplImage* pImgYCrCb = cvCreateImage(cvSize(pFrameInfo->nWidth, pFrameInfo->nHeight), 8, 3);//得到图像的Y分量
yv12toYUV(pImgYCrCb->imageData, pBuffer, pFrameInfo->nWidth, pFrameInfo->nHeight, pImgYCrCb->widthStep);//得到全部RGB图像
spdlog::info("图片数据width={}, height={}, 行数={}", pFrameInfo->nWidth, pFrameInfo->nHeight, pImgYCrCb->widthStep);
IplImage* pImg = cvCreateImage(cvSize(pFrameInfo->nWidth, pFrameInfo->nHeight), 8, 3); //rbg 图像
cvCvtColor(pImgYCrCb, pImg, CV_YCrCb2RGB);
//拿到img对象 组装mat
cv::Mat mat = cv::cvarrToMat(pImg, true);
cvReleaseImage(&pImgYCrCb);
cvReleaseImage(&pImg);
long time2 = clock();
spdlog::info("yv12转mat起始时间={},结束时间={},耗时{}", time1, time2, time2 - time1);

//使用imwrite保存jpeg文件
char fileName[256] = { 0 };
sprintf_s(fileName, "../pic/%d_%d_%d.jpg", pFrameInfo->nWidth, pFrameInfo->nHeight, pFrameInfo->nStamp);
std::vector<int> compression_params;
compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
compression_params.push_back(98); //jpeg压缩质量 0-100 100无压缩
imwrite(fileName, mat, compression_params);
spdlog::info("图片帧获取成功, 图像帧率={},图像帧数={}", pFrameInfo->nFrameRate, pFrameInfo->dwFrameNum);
}

//需要把YV12转换错JPEG、bmp
break;

case T_AUDIO16:
//音频数据
break;
default:
break;
}
}

yv12转rbg

参考网址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void yv12toYUV(char* outYuv, char* inYv12, int width, int height, int widthStep)
{
int col, row;
unsigned int Y, U, V;
int tmp;
int idx;
for (row = 0; row < height; row++)
{
for (col = 0; col < width; col++)
{
tmp = (row / 2) * (width / 2) + (col / 2);
Y = (unsigned int)inYv12[row * width + col];
U = (unsigned int)inYv12[width * height + width * height / 4 + tmp];
V = (unsigned int)inYv12[width * height + tmp];
outYuv[idx + col * 3] = Y;
outYuv[idx + col * 3 + 1] = U;
outYuv[idx + col * 3 + 2] = V;
}
}
}
文章目录
  1. 1. SDK下载
    1. 1.1. 设备网络SDK下载
    2. 1.2. 播放库SDK下载
  2. 2. 代码
    1. 2.1. 初始化设备连接
    2. 2.2. 实时预览回调函数
    3. 2.3. 设置解码回调函数
    4. 2.4. yv12转rbg