海康威视监控相机实时性研究

发布于:2025-07-12 ⋅ 阅读:(9) ⋅ 点赞:(0)

一. 海康威视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()


网站公告

今日签到

点亮在社区的每一天
去签到