通过这个帖子,能了解到如何用Python调用海康SDK,实现业务逻辑需要结合哪些资料,这些接口的参数是怎么样的,如何翻译成Python,如何传参,参数中的一些变量,常量可以怎样查找。

源码传送门 github仓库readme中有社区群联系方式可供大家参与讨论,目前已经有数百人

开发资源

海康威视SDK下载 https://www.hikvision.com/cn/download_61.html

DK只有对linux和windows的支持,没有对mac的支持,所以mac开发比较累

基于SDK开发

  1. 从官方给到的SDK中有.chm的文件,是一个接口文档,里面详细的介绍了该SDK的所有描述。
  2. SDK中给到了基于Java,C#等demo,但是没有python。(这些demo可以帮我们理解一些比较晦涩难懂的逻辑,但是demo写的也没有很严格,部分变量类型定义不是完全按照文档来的,需要自己消化)
  3. linux和windows的SDK中分别是.so和.dll,对于python我们需要ctypes库来完成二次开发
  4. 官方开发文档,这里有详细硬件功能调用链,很详细,不过demo是c++的,另外demo中的一些变量或者常量不能查看引用,所以可以与第一项中提到的文档结合,如果两者描述不符,以当前SDK中的.chm文件优先。
  5. https://open.hikvision.com/hardware/definitions/{接口或实体}.html
    这个是在线接口详细文档。

代码实践

目前开源库以更新迭代,以下为原始代码,如果需要用到用到Python开发,可以直接使用开源库。
  1. 基础SDK调用实现
from ctypes import *
import os
import logging
import hkws.model.login as login
import hkws.model.preview as preview

from hkws.callback import hikFunc

from hkws.callback import g_real_data_call_back


class HKAdapter:
    so_list = []

    # 加载目录下所有so文件
    def add_lib(self, path, suffix):
        files = os.listdir(path)
        for file in files:
            if not os.path.isdir(path + file):
                if file.endswith(suffix):
                    self.so_list.append(path + file)
            else:
                self.add_lib(path + file + "/", suffix)

    # python 调用 sdk 指定方法
    def call_cpp(self, func_name, *args):
        for so_lib in self.so_list:
            try:
                lib = cdll.LoadLibrary(so_lib)
                try:
                    value = eval("lib.%s" % func_name)(*args)
                    logging.info("调用的库:" + so_lib)
                    logging.info("执行成功,返回值:" + str(value))
                    return value
                except:
                    continue
            except:
                continue
            # logging.info("库文件载入失败:" + so_lib )
        logging.error("没有找到接口!")
        return False

 

  1. 初始化SDK及释放SDK
    # 初始化海康微视 sdk
    def init_sdk(self):
        init_res = self.call_cpp("NET_DVR_Init")  # SDK初始化
        if init_res:
            logging.info("SDK初始化成功")
            return True
        else:
            error_info = self.call_cpp("NET_DVR_GetLastError")
            logging.error("SDK初始化错误:" + str(error_info))
            return False

    # 释放sdk
    def sdk_clean(self):
        result = self.call_cpp("NET_DVR_Cleanup")
        logging.info("释放资源", result)
  1. 用户设备登录
请求所用参数,这里需要用python ctypes参照
https://open.hikvision.com/hardware/definitions/NET_DVR_Login_V40.html 
所给出的结构写出对应的python类,有些常量具体数值是没有的,需要结合之前所说的Java,C#的demo看。
class NET_DVR_USER_LOGIN_INFO(Structure):
    _fields_ = [
        ("sDeviceAddress", c_byte * 129),  # 设备地址,IP或者普通域名
        ("byUseTransport", c_byte),  # 是否启用能力透传 0:不启动,默认  1:启动
        ("wPort", c_uint16),  # 设备端口号
        ("sUserName", c_byte * 64),  # 登录用户名
        ("sPassword", c_byte * 64),  # 登录密码
        # ("fLoginResultCallBack",)  #

        ("bUseAsynLogin", c_bool),  # 是否异步登录, 0:否 1:是
        ("byProxyType", c_byte),  # 代理服务器类型:0- 不使用代理,1- 使用标准代理,2- 使用EHome代理

        # 是否使用UTC时间:
        # 0 - 不进行转换,默认;
        # 1 - 输入输出UTC时间,SDK进行与设备时区的转换;
        # 2 - 输入输出平台本地时间,SDK进行与设备时区的转换
        ("byUseUTCTime", c_byte),
        # 登录模式(不同模式具体含义详见“Remarks”说明):
        # 0- SDK私有协议,
        # 1- ISAPI协议,
        # 2- 自适应(设备支持协议类型未知时使用,一般不建议)
        ("byLoginMode", c_byte),
        # ISAPI协议登录时是否启用HTTPS(byLoginMode为1时有效):
        # 0 - 不启用,
        # 1 - 启用,
        # 2 - 自适应(设备支持协议类型未知时使用,一般不建议)
        ("byHttps", c_byte),
        # 代理服务器序号,添加代理服务器信息时相对应的服务器数组下表值
        ("iProxyID", c_long),
        # 保留,置为0
        ("byRes3", c_byte * 120),
    ]


# 设备参数结构体。
class NET_DVR_DEVICEINFO_V30(Structure):
    _fields_ = [
        ("sSerialNumber", c_byte * 48),  # 序列号
        ("byAlarmInPortNum", c_byte),  # 模拟报警输入个数
        ("byAlarmOutPortNum", c_byte),  # 模拟报警输出个数
        ("byDiskNum", c_byte),  # 硬盘个数
        ("byDVRType", c_byte),  # 设备类型,详见下文列表
        ("byChanNum", c_byte),  # 设备模拟通道个数,数字(IP)通道最大个数为byIPChanNum + byHighDChanNum*256
        ("byStartChan", c_byte),  # 模拟通道的起始通道号,从1开始。数字通道的起始通道号见下面参数byStartDChan
        ("byAudioChanNum", c_byte),  # 设备语音对讲通道数
        ("byIPChanNum", c_byte),
        # 设备最大数字通道个数,低8位,搞8位见byHighDChanNum. 可以根据ip通道个数是否调用NET_DVR_GetDVRConfig (配置命令NET_DVR_GET_IPPARACFG_V40)获得模拟和数字通道的相关参数
        ("byZeroChanNum", c_byte),  # 零通道编码个数
        ("byMainProto", c_byte),  # 主码流传输协议类型: 0 - private, 1 - rtsp, 2- 同时支持私有协议和rtsp协议去留(默认采用私有协议取流)
        ("bySubProto", c_byte),  # 字码流传输协议类型: 0 - private , 1 - rtsp , 2 - 同时支持私有协议和rtsp协议取流 (默认采用私有协议取流)

        # 能力,位与结果为0表示不支持,1
        # 表示支持
        # bySupport & 0x1,表示是否支持智能搜索
        # bySupport & 0x2,表示是否支持备份
        # bySupport & 0x4,表示是否支持压缩参数能力获取
        # bySupport & 0x8, 表示是否支持双网卡
        # bySupport & 0x10, 表示支持远程SADP
        # bySupport & 0x20, 表示支持Raid卡功能
        # bySupport & 0x40, 表示支持IPSAN目录查找
        # bySupport & 0x80, 表示支持rtp over rtsp
        ("bySupport", c_byte),
        # 能力集扩充,位与结果为0表示不支持,1
        # 表示支持
        # bySupport1 & 0x1, 表示是否支持snmp
        # v30
        # bySupport1 & 0x2, 表示是否支持区分回放和下载
        # bySupport1 & 0x4, 表示是否支持布防优先级
        # bySupport1 & 0x8, 表示智能设备是否支持布防时间段扩展
        # bySupport1 & 0x10, 表示是否支持多磁盘数(超过33个)
        # bySupport1 & 0x20, 表示是否支持rtsp over http
        # bySupport1 & 0x80, 表示是否支持车牌新报警信息,且还表示是否支持NET_DVR_IPPARACFG_V40配置
        ("bySupport1", c_byte),
        # 能力集扩充,位与结果为0表示不支持,1
        # 表示支持
        # bySupport2 & 0x1, 表示解码器是否支持通过URL取流解码
        # bySupport2 & 0x2, 表示是否支持FTPV40
        # bySupport2 & 0x4, 表示是否支持ANR(断网录像)
        # bySupport2 & 0x20, 表示是否支持单独获取设备状态子项
        # bySupport2 & 0x40, 表示是否是码流加密设备
        ("bySupport2", c_byte),
        ("wDevType", c_uint16),  # 设备型号,详见下文列表
        # 能力集扩展,位与结果:0 - 不支持,1 - 支持
        # bySupport3 & 0x1, 表示是否支持多码流
        # bySupport3 & 0x4, 表示是否支持按组配置,具体包含通道图像参数、报警输入参数、IP报警输入 / 输出接入参数、用户参数、设备工作状态、JPEG抓图、定时和时间抓图、硬盘盘组管理等
        # bySupport3 & 0x20,表示是否支持通过DDNS域名解析取流
        ("bySupport3", c_byte),
        # 是否支持多码流,按位表示,位与结果:0 - 不支持,1 - 支持
        # byMultiStreamProto & 0x1, 表示是否支持码流3
        # byMultiStreamProto & 0x2, 表示是否支持码流4
        # byMultiStreamProto & 0x40, 表示是否支持主码流
        # byMultiStreamProto & 0x80, 表示是否支持子码流
        ("byMultiStreamProto", c_byte),
        ("byStartDChan", c_byte),  # 起始数字通道号,0表示无数字通道,比如DVR或IPC
        ("byStartDTalkChan", c_byte),  # 起始数字对讲通道号,区别于模拟对讲通道号,0表示无数字对讲通道
        ("byHighDChanNum", c_byte),  # 数字通道个数,高8位

        # 能力集扩展,按位表示,位与结果:0 - 不支持,1 - 支持
        # bySupport4 & 0x01, 表示是否所有码流类型同时支持RTSP和私有协议
        # bySupport4 & 0x10, 表示是否支持域名方式挂载网络硬盘
        ("bySupport4", c_byte),
        # 支持语种能力,按位表示,位与结果:0 - 不支持,1 - 支持
        # byLanguageType == 0,表示老设备,不支持该字段
        # byLanguageType & 0x1,表示是否支持中文
        # byLanguageType & 0x2,表示是否支持英文
        ("byLanguageType", c_byte),

        ("byVoiceInChanNum", c_byte),  # 音频输入通道数
        ("byStartVoiceInChanNo", c_byte),  # 音频输入起始通道号,0表示无效
        ("byRes3", c_byte * 2),  # 保留,置为0
        ("byMirrorChanNum", c_byte),  # 镜像通道个数,录播主机中用于表示导播通道
        ("wStartMirrorChanNo", c_uint16),  # 起始镜像通道号
        ("byRes2", c_byte * 2)]  # 保留,置为0


class NET_DVR_DEVICEINFO_V40(Structure):
    _fields_ = [
        ("struDeviceV30", NET_DVR_DEVICEINFO_V30),  # 设备参数
        ("bySupportLock", c_byte),  # 设备是否支持锁定功能,bySuportLock 为1时,dwSurplusLockTime和byRetryLoginTime有效
        ("byRetryLoginTime", c_byte),  # 剩余可尝试登陆的次数,用户名,密码错误时,此参数有效

        # 密码安全等级: 0-无效,1-默认密码,2-有效密码,3-风险较高的密码,
        # 当管理员用户的密码为出厂默认密码(12345)或者风险较高的密码时,建议上层客户端提示用户名更改密码
        ("byPasswordLevel", c_byte),

        ("byProxyType", c_byte),  # 代理服务器类型,0-不使用代理,1-使用标准代理,2-使用EHome代理
        # 剩余时间,单位:秒,用户锁定时次参数有效。在锁定期间,用户尝试登陆,不算用户名密码输入对错
        # 设备锁定剩余时间重新恢复到30分钟
        ("dwSurplusLockTime", c_ulong),
        # 字符编码类型(SDK所有接口返回的字符串编码类型,透传接口除外):
        # 0 - 无字符编码信息(老设备)
        # 1 - GB2312
        ("byCharEncodeType", c_byte),
        # 支持v50版本的设备参数获取,设备名称和设备类型名称长度扩展为64字节
        ("bySupportDev5", c_byte),
        # 登录模式(不同的模式具体含义详见"Remarks"说明:0- SDK私有协议,1- ISAPI协议)
        ("byLoginMode", c_byte),
        # 保留,置为0
        ("byRes2", c_byte * 253),
    ]


class NET_DVR_Login_V40(Structure):
    _fields_ = [
        ("pLoginInfo", NET_DVR_USER_LOGIN_INFO),
        ("lpDeviceInfo", NET_DVR_DEVICEINFO_V40)
    ]

 

 # 用户登录指定摄像机设备
    def login(self, address="192.168.1.1", port=8000, user="admin", pwd="admin"):
        # 设置连接时间
        set_overtime = self.call_cpp("NET_DVR_SetConnectTime", 5000, 4)  # 设置超时
        if set_overtime:
            logging.info(address + ", 设置超时时间成功")
        else:
            error_info = self.call_cpp("NET_DVR_GetLastError")
            logging.error(address + ", 设置超时错误信息:" + str(error_info))
            return False
        # 设置重连
        self.call_cpp("NET_DVR_SetReconnect", 10000, True)

        b_address = bytes(address, "ascii")
        b_user = bytes(user, "ascii")
        b_pwd = bytes(pwd, "ascii")

        struLoginInfo = login.NET_DVR_USER_LOGIN_INFO()
        struLoginInfo.bUseAsynLogin = 0  # 同步登陆
        i = 0
        for o in b_address:
            struLoginInfo.sDeviceAddress[i] = o
            i += 1

        struLoginInfo.wPort = port
        i = 0
        for o in b_user:
            struLoginInfo.sUserName[i] = o
            i += 1

        i = 0
        for o in b_pwd:
            struLoginInfo.sPassword[i] = o
            i += 1

        device_info = login.NET_DVR_DEVICEINFO_V40()
        loginInfo1 = byref(struLoginInfo)
        loginInfo2 = byref(device_info)
        user_id = self.call_cpp("NET_DVR_Login_V40", loginInfo1, loginInfo2)
        logging.info(address + ", 登录结果:" + str(user_id))
        if user_id == -1:  # -1表示失败,其他值表示返回的用户ID值。
            error_info = self.call_cpp("NET_DVR_GetLastError")
            logging.error(address + ", 登录错误信息:" + str(error_info))

        return user_id

 

  1. 调用网络摄像机获得视频数据流
这里会有callback的概念,这里是针对视频流的回调,得到视频流后可以自定义视频流的处理,比如直接对接openCV等
# 定义callback
@CFUNCTYPE(None, c_long, c_ulong, c_byte, c_ulong, c_ulong)
def g_real_data_call_back(lRealPlayHandle: c_long,
                          dwDataType: c_ulong,
                          pBuffer: c_byte,
                          dwBufSize: c_ulong,
                          dwUser: c_ulong):
    print('callback pBufSize is ', lRealPlayHandle, dwBufSize)


# 预览参数结构体
class NET_DVR_PREVIEWINFO(Structure):
    _fields_ = [
        # 通道号,目前设备模拟通道号从1开始,数字通道的起始通道号通过
        # NET_DVR_GetDVRConfig(配置命令NET_DVR_GET_IPPARACFG_V40)获取(dwStartDChan)
        ('lChannel', c_long),
        # 码流类型:0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
        ('dwStreamType', c_ulong),
        # 连接方式:0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RTP/HTTP,6-HRUDP(可靠传输)
        ('dwLinkMode', c_ulong),
        # 播放窗口的句柄,为NULL表示不解码显示
        ('hPlayWnd', c_void_p),
        # 0-非阻塞取流,1- 阻塞取流
        # 若设为不阻塞,表示发起与设备的连接就认为连接成功,如果发生码流接收失败、播放失败等
        # 情况以预览异常的方式通知上层。在循环播放的时候可以减短停顿的时间,与NET_DVR_RealPlay
        # 处理一致。
        # 若设为阻塞,表示直到播放操作完成才返回成功与否,网络异常时SDK内部connect失败将会有5s
        # 的超时才能够返回,不适合于轮询取流操作。
        ('bBlocked', c_bool),
        # 是否启用录像回传: 0-不启用录像回传,1-启用录像回传。ANR断网补录功能,
        # 客户端和设备之间网络异常恢复之后自动将前端数据同步过来,需要设备支持。
        ('bPassbackRecord', c_bool),
        # 延迟预览模式:0-正常预览,1-延迟预览
        ('byPreviewMode', c_byte),
        # 流ID,为字母、数字和"_"的组合,IChannel为0xffffffff时启用此参数
        ('byStreamID', c_byte * 32),
        # 应用层取流协议:0-私有协议,1-RTSP协议。
        # 主子码流支持的取流协议通过登录返回结构参数NET_DVR_DEVICEINFO_V30的byMainProto、bySubProto值得知。
        # 设备同时支持私协议和RTSP协议时,该参数才有效,默认使用私有协议,可选RTSP协议。
        ('byProtoType', c_byte),
        # 保留,置为0
        ('byRes1', c_byte),
        # 码流数据编解码类型:0-通用编码数据,1-热成像探测器产生的原始数据
        # (温度数据的加密信息,通过去加密运算,将原始数据算出真实的温度值)
        ('byVideoCodingType', c_byte),
        # 播放库播放缓冲区最大缓冲帧数,取值范围:1、6(默认,自适应播放模式)   15:置0时默认为1
        ('dwDisplayBufNum', c_ulong),
        # 保留,置为0
        ('byRes', c_byte * 216),
    ]
视频流回调可以在NET_DVR_RealPlay_V40中直接得到,也可以用以下返回的lRealPlayHandle去调用callback_real_data()获得,回调所得的数据可以在回调函数里面操作
    def start_preview(self, cbFunc: hikFunc, userId=0):
        req = preview.NET_DVR_PREVIEWINFO()
        req.hPlayWnd = None
        req.lChannel = 1  # 预览通道号
        req.dwStreamType = 0  # 码流类型:0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
        req.dwLinkMode = 0  # 连接方式:0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RTP/HTTP,6-HRUDP(可靠传输)
        req.bBlocked = 1  # 0-非阻塞 1-阻塞
        struPlayInfo = byref(req)
        # 这个回调函数不适合长时间占用
        # fRealDataCallBack_V30 = preview.REALDATACALLBACK

        lRealPlayHandle = self.call_cpp("NET_DVR_RealPlay_V40", userId, struPlayInfo, cbFunc, None)
        print("start_preview lrealPlayHandle is ", lRealPlayHandle)
        if lRealPlayHandle < 0:
            self.logout(userId)
            self.sdk_clean()
        return lRealPlayHandle

    def stop_preview(self, lRealPlayHandle):
        self.call_cpp("NET_DVR_StopRealPlay", lRealPlayHandle)

    def callback_real_data(self, lRealPlayHandle: c_long, cbFunc: g_real_data_call_back, dwUser: c_ulong):
        return self.call_cpp("NET_DVR_SetRealDataCallBack", lRealPlayHandle, cbFunc, dwUser)

 

作者:太白菜Rennbon
链接:https://www.jianshu.com/p/c3c4bf3d1ef8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。