海康威视监控相机实时性研究
文章目录
- 一. 海康威视IVMS-4200自带软件延迟
- 二. SDK解码播放延迟
- 三. 软件解码(opencv)播放延迟
- 四. 使用python调用c库的dll
一. 海康威视IVMS-4200自带软件延迟
大概300ms左右
二. SDK解码播放延迟
大概250ms左右
代码地址: https://download.csdn.net/download/qq_30150579/87578614
三. 软件解码(opencv)播放延迟
大概1000ms
#include <stdio.h>
#include <iostream>
#include "Windows.h"
#include "HCNetSDK.h"
#include "plaympeg4.h"
#include <time.h>
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;//数据解码回调函数,将YV_12格式的视频数据流转码为可供opencv处理的BGR类型的图片数据。
void CALLBACK DecCBFun(long nPort, char* pBuf, long nSize, FRAME_INFO* pFrameInfo, long nUser, long nReserved2)
{if (pFrameInfo->nType == T_YV12){cv::Mat BGRImage(pFrameInfo->nHeight, pFrameInfo->nWidth, CV_8UC3);cv::Mat YUVImage(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, (unsigned char*)pBuf);cvtColor(YUVImage, BGRImage, cv::COLOR_YUV2BGR_YV12);cv::imshow("1",BGRImage);cv::waitKey(1);}
}// 回调函数,用于处理从海康相机获取的图像数据
void CALLBACK fRealDataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, void* pUser)
{switch (dwDataType){case NET_DVR_SYSHEAD: //获取的数据为系统头static LONG nPort;std::cout << "获取到系统头" << std::endl;std::cout << "1" << std::endl;if (!PlayM4_GetPort(&nPort)) //获取播放库未使用的通道号{std::cout << "获取播放库通道失败" << std::endl;std::cout << "2" << std::endl;break;}else{std::cout << "获取播放库通道成功" << std::endl;std::cout << "3" << std::endl;}if (dwBufSize > 0){if (!PlayM4_SetStreamOpenMode(nPort, STREAME_REALTIME)) //设置实时流播放模式{std::cout << u8"设置实时流播放失败" << std::endl;std::cout << "4" << std::endl;break;}else{std::cout << "设置实时流播放成功" << std::endl;std::cout << "5" << std::endl;}if (!PlayM4_OpenStream(nPort, pBuffer, dwBufSize, 1024 * 1024)) //打开流接口{std::cout << "打开流接口失败" << std::endl;std::cout << "6" << std::endl;break;}else{std::cout << "打开流接口成功" << std::endl;std::cout << "7" << std::endl;}if (!PlayM4_SetDecCallBackExMend(nPort, DecCBFun, NULL, 0, NULL)) //设置播放流的回调函数{std::cout << "设置回调函数失败" << std::endl;std::cout << "8" << std::endl;break;}else{std::cout << "设置回调函数成功" << std::endl;std::cout << "9" << std::endl;}if (!PlayM4_Play(nPort, NULL)) //开始播放{std::cout << "设置播放失败" << std::endl;std::cout << "10" << std::endl;break;}else{std::cout << "设置播放成功" << std::endl;std::cout << "11" << std::endl;}}case NET_DVR_STREAMDATA: //获取的数据为码流数据if (dwBufSize > 0 && nPort != -1){if (!PlayM4_InputData(nPort, pBuffer, dwBufSize)) //将数据送入播放器{break;}}}
}void CALLBACK g_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{char tempbuf[256] = {0};switch(dwType){case EXCEPTION_RECONNECT: //预览时重连printf("----------reconnect--------%d\n", time(NULL));break;default:break;}
}int main() {// 初始化海康SDKNET_DVR_Init();//设置连接时间与重连时间NET_DVR_SetConnectTime(2000, 1);NET_DVR_SetReconnect(10000, true);//设置异常消息回调函数NET_DVR_SetExceptionCallBack_V30(0, NULL, g_ExceptionCallBack, NULL);// 注册设备LONG lUserID;//登录参数,包括设备地址、登录用户、密码等NET_DVR_USER_LOGIN_INFO struLoginInfo = {0};struLoginInfo.bUseAsynLogin = 0; //同步登录方式strcpy(struLoginInfo.sDeviceAddress, "192.168.1.64"); //设备IP地址struLoginInfo.wPort = 8000; //设备服务端口strcpy(struLoginInfo.sUserName, "admin"); //设备登录用户名strcpy(struLoginInfo.sPassword, "nflg1234"); //设备登录密码//设备信息, 输出参数NET_DVR_DEVICEINFO_V40 struDeviceInfoV40 = {0};lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfoV40);if (lUserID < 0){printf("Login failed, error code: %d\n", NET_DVR_GetLastError());NET_DVR_Cleanup();return 0;}//启动预览并设置回调数据流LONG lRealPlayHandle;NET_DVR_PREVIEWINFO struPlayInfo = {0};struPlayInfo.hPlayWnd = NULL; //需要SDK解码时句柄设为有效值,仅取流不解码时可设为空struPlayInfo.lChannel = 1; //预览通道号struPlayInfo.dwStreamType = 0; //0-主码流,1-子码流,2-码流3,3-码流4,以此类推struPlayInfo.dwLinkMode = 0; //0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTPstruPlayInfo.bBlocked = 1; //0- 非阻塞取流,1- 阻塞取流lRealPlayHandle = NET_DVR_RealPlay_V40(lUserID, &struPlayInfo, fRealDataCallBack, NULL);if (lRealPlayHandle < 0){printf("NET_DVR_RealPlay_V40 error, %d\n", NET_DVR_GetLastError());NET_DVR_Logout(lUserID);NET_DVR_Cleanup();return 0;}// 主循环:从全局列表中获取图像并显示while (true){}// 释放资源NET_DVR_StopRealPlay(lRealPlayHandle);NET_DVR_Logout(lUserID);NET_DVR_Cleanup();DeleteCriticalSection(&g_cs_frameList);return 0;
}
四. 使用python调用c库的dll
大概300ms
# coding=utf-8
import timefrom HCNetSDK import *
from PlayCtrl import *class devClass:def __init__(self):self.hikSDK, self.playM4SDK = self.LoadSDK() # 加载sdk库self.iUserID = -1 # 登录句柄self.lRealPlayHandle = -1 # 预览句柄self.wincv = None # windows环境下的参数self.win = None # 预览窗口self.FuncDecCB = None # 解码回调self.PlayCtrlPort = C_LONG(-1) # 播放通道号self.basePath = '' # 基础路径self.preview_file = '' # linux预览取流保存路径self.funcRealDataCallBack_V30 = REALDATACALLBACK(self.RealDataCallBack_V30) # 预览回调函数# self.msg_callback_func = MSGCallBack_V31(self.g_fMessageCallBack_Alarm) # 注册回调函数实现def LoadSDK(self):hikSDK = NoneplayM4SDK = Nonetry:print("netsdkdllpath: ", netsdkdllpath)hikSDK = load_library(netsdkdllpath)playM4SDK = load_library(playM4dllpath)except OSError as e:print('动态库加载失败', e)return hikSDK, playM4SDK# 设置SDK初始化依赖库路径def SetSDKInitCfg(self):# 设置HCNetSDKCom组件库和SSL库加载路径if sys_platform == 'windows':basePath = os.getcwd().encode('gbk')strPath = basePath + b'\lib'sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()sdk_ComPath.sPath = strPathprint('strPath: ', strPath)if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SDK_PATH.value,byref(sdk_ComPath)):print('NET_DVR_SetSDKInitCfg: 2 Succ')if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_LIBEAY_PATH.value,create_string_buffer(strPath + b'\libcrypto-1_1-x64.dll')):print('NET_DVR_SetSDKInitCfg: 3 Succ')if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SSLEAY_PATH.value,create_string_buffer(strPath + b'\libssl-1_1-x64.dll')):print('NET_DVR_SetSDKInitCfg: 4 Succ')else:basePath = os.getcwd().encode('utf-8')strPath = basePath + b'\lib'sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()sdk_ComPath.sPath = strPathif self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SDK_PATH.value,byref(sdk_ComPath)):print('NET_DVR_SetSDKInitCfg: 2 Succ')if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_LIBEAY_PATH.value,create_string_buffer(strPath + b'/libcrypto.so.1.1')):print('NET_DVR_SetSDKInitCfg: 3 Succ')if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SSLEAY_PATH.value,create_string_buffer(strPath + b'/libssl.so.1.1')):print('NET_DVR_SetSDKInitCfg: 4 Succ')self.basePath = basePath# 通用设置,日志/回调事件类型等def GeneralSetting(self):# 日志的等级(默认为0):0-表示关闭日志,1-表示只输出ERROR错误日志,2-输出ERROR错误信息和DEBUG调试信息,3-输出ERROR错误信息、DEBUG调试信息和INFO普通信息等所有信息# self.hikSDK.NET_DVR_SetLogToFile(3, b'./SdkLog_Python/', False)self.hikSDK.NET_DVR_SetLogToFile(3, bytes('./SdkLog_Python/', encoding="utf-8"), False)# 登录设备def LoginDev(self, ip, username, pwd):# 登录参数,包括设备地址、登录用户、密码等struLoginInfo = NET_DVR_USER_LOGIN_INFO()struLoginInfo.bUseAsynLogin = 0 # 同步登录方式struLoginInfo.sDeviceAddress = ip # 设备IP地址struLoginInfo.wPort = 8000 # 设备服务端口struLoginInfo.sUserName = username # 设备登录用户名struLoginInfo.sPassword = pwd # 设备登录密码struLoginInfo.byLoginMode = 0# 设备信息, 输出参数struDeviceInfoV40 = NET_DVR_DEVICEINFO_V40()self.iUserID = self.hikSDK.NET_DVR_Login_V40(byref(struLoginInfo), byref(struDeviceInfoV40))if self.iUserID < 0:print("Login failed, error code: %d" % self.hikSDK.NET_DVR_GetLastError())self.hikSDK.NET_DVR_Cleanup()else:print('登录成功,设备序列号:%s' % str(struDeviceInfoV40.struDeviceV30.sSerialNumber, encoding="utf8").rstrip('\x00'))# 登出设备def LogoutDev(self):if self.iUserID > -1:# 撤销布防,退出程序时调用self.hikSDK.NET_DVR_Logout(self.iUserID)def DecCBFun(self, nPort, pBuf, nSize, pFrameInfo, nUser, nReserved2):# 解码回调函数if pFrameInfo.contents.nType == 3:# 解码返回视频YUV数据,将YUV数据转成jpg图片保存到本地# 如果有耗时处理,需要将解码数据拷贝到回调函数外面的其他线程里面处理,避免阻塞回调导致解码丢帧sFileName = ('./pic/test_stamp[%d].jpg' % pFrameInfo.contents.nStamp)nWidth = pFrameInfo.contents.nWidthnHeight = pFrameInfo.contents.nHeightnType = pFrameInfo.contents.nTypedwFrameNum = pFrameInfo.contents.dwFrameNumnStamp = pFrameInfo.contents.nStampprint(nWidth, nHeight, nType, dwFrameNum, nStamp, sFileName)lRet = self.playM4SDK.PlayM4_ConvertToJpegFile(pBuf, nSize, nWidth, nHeight, nType,c_char_p(sFileName.encode()))# if lRet == 0:# print('PlayM4_ConvertToJpegFile fail, error code is:', self.playM4SDK.PlayM4_GetLastError(nPort))# else:# print('PlayM4_ConvertToJpegFile success')# 将视频流保存到本地def writeFile(self, filePath, pBuffer, dwBufSize):# 使用memmove函数将指针数据读到数组中data_array = (c_byte * dwBufSize)()memmove(data_array, pBuffer, dwBufSize)# 判断文件路径是否存在if not os.path.exists(filePath):# 如果不存在,使用 open() 函数创建一个空文件open(filePath, "w").close()preview_file_output = open(filePath, 'ab')preview_file_output.write(data_array)preview_file_output.close()def RealDataCallBack_V30(self, lPlayHandle, dwDataType, pBuffer, dwBufSize, pUser):# 码流回调函数if sys_platform == 'linux':# 码流回调函数if dwDataType == NET_DVR_SYSHEAD:from datetime import datetime# 获取当前时间的datetime对象current_time = datetime.now()timestamp_str = current_time.strftime('%Y%m%d_%H%M%S')self.preview_file = f'./previewVideo{timestamp_str}.mp4'elif dwDataType == NET_DVR_STREAMDATA:self.writeFile(self.preview_file, pBuffer, dwBufSize)else:print(u'其他数据,长度:', dwBufSize)elif sys_platform == 'windows':if dwDataType == NET_DVR_SYSHEAD:# 设置流播放模式self.playM4SDK.PlayM4_SetStreamOpenMode(self.PlayCtrlPort, 0)# 打开码流,送入40字节系统头数据if self.playM4SDK.PlayM4_OpenStream(self.PlayCtrlPort, pBuffer, dwBufSize, 1024 * 1024):# 设置解码回调,可以返回解码后YUV视频数据self.FuncDecCB = DECCBFUNWIN(self.DecCBFun)self.playM4SDK.PlayM4_SetDecCallBackExMend(self.PlayCtrlPort, self.FuncDecCB, None, 0, None)# 开始解码播放if self.playM4SDK.PlayM4_Play(self.PlayCtrlPort, self.wincv.winfo_id()):print(u'播放库播放成功')else:print(u'播放库播放失败')else:print(f'播放库打开流失败, 错误码:{self.playM4SDK.PlayM4_GetLastError(self.PlayCtrlPort)}')elif dwDataType == NET_DVR_STREAMDATA:self.playM4SDK.PlayM4_InputData(self.PlayCtrlPort, pBuffer, dwBufSize)else:print(u'其他数据,长度:', dwBufSize)def startPlay(self, playTime):# 获取一个播放句柄if not self.playM4SDK.PlayM4_GetPort(byref(self.PlayCtrlPort)):print(f'获取播放库句柄失败, 错误码:{self.playM4SDK.PlayM4_GetLastError(self.PlayCtrlPort)}')if sys_platform == 'linux':# 开始预览preview_info = NET_DVR_PREVIEWINFO()preview_info.hPlayWnd = 0preview_info.lChannel = 1 # 通道号preview_info.dwStreamType = 0 # 主码流preview_info.dwLinkMode = 0 # TCPpreview_info.bBlocked = 1 # 阻塞取流# 开始预览并且设置回调函数回调获取实时流数据self.lRealPlayHandle = self.hikSDK.NET_DVR_RealPlay_V40(self.iUserID, byref(preview_info),self.funcRealDataCallBack_V30,None)if self.lRealPlayHandle < 0:print('Open preview fail, error code is: %d' % self.hikSDK.NET_DVR_GetLastError())# 登出设备self.hikSDK.NET_DVR_Logout(self.iUserID)# 释放资源self.hikSDK.NET_DVR_Cleanup()exit()time.sleep(playTime)elif sys_platform == 'windows':import tkinterfrom tkinter import Button# 创建窗口self.win = tkinter.Tk()# 固定窗口大小self.win.resizable(0, 0)self.win.overrideredirect(True)sw = self.win.winfo_screenwidth()# 得到屏幕宽度sh = self.win.winfo_screenheight()# 得到屏幕高度# 窗口宽高ww = 1920wh = 1080x = (sw - ww) / 2y = (sh - wh) / 2self.win.geometry("%dx%d+%d+%d" % (ww, wh, x, y))# 创建退出按键b = Button(self.win, text='退出', command=self.win.quit)b.pack()# 创建一个Canvas,设置其背景色为白色self.wincv = tkinter.Canvas(self.win, bg='white', width=ww, height=wh)self.wincv.pack()# 开始预览preview_info = NET_DVR_PREVIEWINFO()preview_info.hPlayWnd = 0preview_info.lChannel = 1 # 通道号preview_info.dwStreamType = 0 # 主码流preview_info.dwLinkMode = 0 # TCPpreview_info.bBlocked = 1 # 阻塞取流# 开始预览并且设置回调函数回调获取实时流数据self.lRealPlayHandle = self.hikSDK.NET_DVR_RealPlay_V40(self.iUserID, byref(preview_info),self.funcRealDataCallBack_V30,None)if self.lRealPlayHandle < 0:print('Open preview fail, error code is: %d' % self.hikSDK.NET_DVR_GetLastError())# 登出设备self.hikSDK.NET_DVR_Logout(self.iUserID)# 释放资源self.hikSDK.NET_DVR_Cleanup()exit()# show Windowsself.win.mainloop()def stopPlay(self):# 关闭预览self.hikSDK.NET_DVR_StopRealPlay(self.lRealPlayHandle)# 停止解码,释放播放库资源if self.PlayCtrlPort.value > -1:self.playM4SDK.PlayM4_Stop(self.PlayCtrlPort)self.playM4SDK.PlayM4_CloseStream(self.PlayCtrlPort)self.playM4SDK.PlayM4_FreePort(self.PlayCtrlPort)self.PlayCtrlPort = C_LONG(-1)if __name__ == '__main__':dev = devClass()dev.SetSDKInitCfg() # 设置SDK初始化依赖库路径dev.hikSDK.NET_DVR_Init() # 初始化sdkdev.GeneralSetting() # 通用设置,日志,回调函数等dev.LoginDev(ip=b'192.168.1.64', username=b"admin", pwd=b"nflg1234") # 登录设备dev.startPlay(playTime=5) # playTime用于linux环境控制预览时长,windows环境无效dev.stopPlay()dev.LogoutDev()# 释放资源dev.hikSDK.NET_DVR_Cleanup()