一. 海康威视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() {
// 初始化海康SDK
NET_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/HTTP
struPlayInfo.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 time
from 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 = None
playM4SDK = None
try:
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 = strPath
print('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 = 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.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.nWidth
nHeight = pFrameInfo.contents.nHeight
nType = pFrameInfo.contents.nType
dwFrameNum = pFrameInfo.contents.dwFrameNum
nStamp = pFrameInfo.contents.nStamp
print(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 = 0
preview_info.lChannel = 1 # 通道号
preview_info.dwStreamType = 0 # 主码流
preview_info.dwLinkMode = 0 # TCP
preview_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 tkinter
from 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 = 1920
wh = 1080
x = (sw - ww) / 2
y = (sh - wh) / 2
self.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 = 0
preview_info.lChannel = 1 # 通道号
preview_info.dwStreamType = 0 # 主码流
preview_info.dwLinkMode = 0 # TCP
preview_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 Windows
self.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() # 初始化sdk
dev.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()