|
|
@@ -7,7 +7,13 @@ import ctypes
|
|
|
import os
|
|
|
import threading
|
|
|
import queue
|
|
|
-from ctypes import c_int, c_long, c_ulong, c_char_p, c_ubyte, POINTER, Structure, byref, CFUNCTYPE
|
|
|
+from ctypes import c_int, c_long, c_uint32, c_char_p, c_ubyte, POINTER, Structure, byref, CFUNCTYPE
|
|
|
+
|
|
|
+# 大华SDK Linux类型映射 (dhnetsdk.h非Windows定义):
|
|
|
+# DWORD = unsigned int (4 bytes) → c_uint32
|
|
|
+# LONG = int (4 bytes) → c_int
|
|
|
+# LLONG = long (8 bytes on LP64) → c_long
|
|
|
+# LDWORD = long (8 bytes on LP64) → c_long
|
|
|
from typing import Optional, Callable, Tuple, Dict, Any
|
|
|
|
|
|
|
|
|
@@ -73,24 +79,48 @@ class DahuaSDK:
|
|
|
def _setup_structures(self):
|
|
|
"""设置SDK所需的C结构体"""
|
|
|
|
|
|
- # 登录输入参数
|
|
|
+ # 登录输入参数 (必须与SDK头文件 NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY 严格对齐)
|
|
|
class NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY(Structure):
|
|
|
_fields_ = [
|
|
|
- ('dwSize', c_ulong),
|
|
|
- ('szIP', ctypes.c_char * 64),
|
|
|
- ('nPort', c_int),
|
|
|
- ('szUserName', ctypes.c_char * 64),
|
|
|
- ('szPassword', ctypes.c_char * 64),
|
|
|
- ('emSpecCap', c_int),
|
|
|
+ ('dwSize', c_uint32), # DWORD = uint32
|
|
|
+ ('szIP', ctypes.c_char * 64), # IP
|
|
|
+ ('nPort', c_int), # 端口
|
|
|
+ ('szUserName', ctypes.c_char * 64), # 用户名
|
|
|
+ ('szPassword', ctypes.c_char * 64), # 密码
|
|
|
+ ('emSpecCap', c_int), # 登录模式
|
|
|
+ ('byReserved', ctypes.c_ubyte * 4), # 对齐保留
|
|
|
+ ('pCapParam', ctypes.c_void_p), # 扩展参数
|
|
|
+ ('emTLSCap', c_int), # TLS模式
|
|
|
+ ('szLocalIP', ctypes.c_char * 64), # 本地IP
|
|
|
+ ]
|
|
|
+
|
|
|
+ # 设备信息 (NET_DEVICEINFO_Ex)
|
|
|
+ class NET_DEVICEINFO_Ex(Structure):
|
|
|
+ _fields_ = [
|
|
|
+ ('sSerialNumber', ctypes.c_ubyte * 48), # 序列号
|
|
|
+ ('nAlarmInPortNum', c_int), # 报警输入数
|
|
|
+ ('nAlarmOutPortNum', c_int), # 报警输出数
|
|
|
+ ('nDiskNum', c_int), # 硬盘数
|
|
|
+ ('nDVRType', c_int), # DVR类型
|
|
|
+ ('nChanNum', c_int), # 通道数
|
|
|
+ ('byLimitLoginTime', ctypes.c_ubyte), # 限制登录时间
|
|
|
+ ('byLeftLogTimes', ctypes.c_ubyte), # 剩余登录次数
|
|
|
+ ('bReserved', ctypes.c_ubyte * 2), # 保留
|
|
|
+ ('nLockLeftTime', c_int), # 锁定剩余时间
|
|
|
+ ('Reserved', ctypes.c_char * 4), # 保留
|
|
|
+ ('nNTlsPort', c_int), # TLS端口
|
|
|
+ ('nKeyFrameEncrypt', c_int), # 关键帧加密
|
|
|
+ ('emAlgorithm', c_int), # 加密算法
|
|
|
+ ('Reserved2', ctypes.c_char * 8), # 保留
|
|
|
]
|
|
|
|
|
|
- # 登录输出参数
|
|
|
+ # 登录输出参数 (必须与SDK头文件严格对齐)
|
|
|
class NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY(Structure):
|
|
|
_fields_ = [
|
|
|
- ('dwSize', c_ulong),
|
|
|
- ('pstuDeviceInfo', ctypes.c_void_p),
|
|
|
- ('nError', c_int),
|
|
|
- ('byRes', ctypes.c_char * 128),
|
|
|
+ ('dwSize', c_uint32), # DWORD = uint32
|
|
|
+ ('stuDeviceInfo', NET_DEVICEINFO_Ex), # 设备信息
|
|
|
+ ('nError', c_int), # 错误码
|
|
|
+ ('byReserved', ctypes.c_char * 132), # 保留
|
|
|
]
|
|
|
|
|
|
# 抓拍参数
|
|
|
@@ -151,50 +181,50 @@ class DahuaSDK:
|
|
|
|
|
|
# === 必需函数 ===
|
|
|
|
|
|
- # CLIENT_Init
|
|
|
- self.sdk.CLIENT_Init.argtypes = [ctypes.c_void_p, c_ulong]
|
|
|
+ # CLIENT_Init(BOOL (CALLBACK *cbDisConnect), LDWORD)
|
|
|
+ self.sdk.CLIENT_Init.argtypes = [ctypes.c_void_p, c_long]
|
|
|
self.sdk.CLIENT_Init.restype = ctypes.c_bool
|
|
|
|
|
|
- # CLIENT_Cleanup
|
|
|
+ # CLIENT_Cleanup()
|
|
|
self.sdk.CLIENT_Cleanup.argtypes = []
|
|
|
self.sdk.CLIENT_Cleanup.restype = None
|
|
|
|
|
|
- # CLIENT_LoginWithHighLevelSecurity
|
|
|
+ # CLIENT_LoginWithHighLevelSecurity -> LLONG
|
|
|
self.sdk.CLIENT_LoginWithHighLevelSecurity.argtypes = [
|
|
|
POINTER(self.NET_IN_LOGIN),
|
|
|
POINTER(self.NET_OUT_LOGIN)
|
|
|
]
|
|
|
self.sdk.CLIENT_LoginWithHighLevelSecurity.restype = c_long
|
|
|
|
|
|
- # CLIENT_Logout
|
|
|
+ # CLIENT_Logout(LLONG)
|
|
|
self.sdk.CLIENT_Logout.argtypes = [c_long]
|
|
|
self.sdk.CLIENT_Logout.restype = ctypes.c_bool
|
|
|
|
|
|
- # CLIENT_RealPlay
|
|
|
+ # CLIENT_RealPlay(LLONG, int, void*) -> LLONG
|
|
|
self.sdk.CLIENT_RealPlay.argtypes = [c_long, c_int, ctypes.c_void_p]
|
|
|
self.sdk.CLIENT_RealPlay.restype = c_long
|
|
|
|
|
|
- # CLIENT_StopRealPlay
|
|
|
+ # CLIENT_StopRealPlay(LLONG) -> BOOL
|
|
|
self.sdk.CLIENT_StopRealPlay.argtypes = [c_long]
|
|
|
self.sdk.CLIENT_StopRealPlay.restype = ctypes.c_bool
|
|
|
|
|
|
- # CLIENT_DHPTZControlEx
|
|
|
+ # CLIENT_DHPTZControlEx(LLONG, int, DWORD, LONG, LONG, LONG, BOOL)
|
|
|
self.sdk.CLIENT_DHPTZControlEx.argtypes = [
|
|
|
- c_long, c_int, c_ulong, c_long, c_long, c_long, ctypes.c_bool
|
|
|
+ c_long, c_int, c_uint32, c_int, c_int, c_int, ctypes.c_bool
|
|
|
]
|
|
|
self.sdk.CLIENT_DHPTZControlEx.restype = ctypes.c_bool
|
|
|
|
|
|
- # CLIENT_SnapPicture
|
|
|
+ # CLIENT_SnapPicture(LLONG, SNAP_PARAMS*) -> BOOL
|
|
|
self.sdk.CLIENT_SnapPicture.argtypes = [c_long, POINTER(self.SNAP_PARAMS)]
|
|
|
self.sdk.CLIENT_SnapPicture.restype = ctypes.c_bool
|
|
|
|
|
|
# === 可选函数(某些SDK版本/平台可能缺失)===
|
|
|
self._optional_funcs = {}
|
|
|
|
|
|
- self._bind_optional('CLIENT_SetSnapRevCallBack', [ctypes.c_void_p, c_ulong], None)
|
|
|
+ self._bind_optional('CLIENT_SetSnapRevCallBack', [ctypes.c_void_p, c_long], None)
|
|
|
self._bind_optional('CLIENT_RealPlayEx', [c_long, c_int, ctypes.c_void_p, c_int], c_long)
|
|
|
self._bind_optional('CLIENT_StopRealPlayEx', [c_long], ctypes.c_bool)
|
|
|
- self._bind_optional('CLIENT_SetVideoProcCallBack', [ctypes.c_void_p, c_ulong], None)
|
|
|
+ self._bind_optional('CLIENT_SetVideoProcCallBack', [ctypes.c_void_p, c_long], None)
|
|
|
|
|
|
def _bind_optional(self, name: str, argtypes: list, restype):
|
|
|
"""绑定可选SDK函数,缺失时设为None而不报错"""
|
|
|
@@ -229,7 +259,7 @@ class DahuaSDK:
|
|
|
if disconnect_callback:
|
|
|
# 保存回调引用,防止被垃圾回收
|
|
|
self._disconnect_callback = ctypes.CFUNCTYPE(
|
|
|
- None, c_long, c_char_p, c_int, c_ulong
|
|
|
+ ctypes.c_bool, c_long, c_char_p, c_int, c_long
|
|
|
)(disconnect_callback)
|
|
|
|
|
|
result = self.sdk.CLIENT_Init(self._disconnect_callback, 0)
|
|
|
@@ -320,14 +350,14 @@ class DahuaSDK:
|
|
|
print("SDK不支持CLIENT_SetVideoProcCallBack,视频回调不可用")
|
|
|
return
|
|
|
|
|
|
- def video_callback(real_handle: c_long, data_type: c_ulong,
|
|
|
- buffer: POINTER(c_ubyte), buf_size: c_ulong,
|
|
|
- param: c_long, user_data: c_ulong):
|
|
|
+ def video_callback(real_handle: c_long, data_type: c_uint32,
|
|
|
+ buffer: POINTER(c_ubyte), buf_size: c_uint32,
|
|
|
+ param: c_long, user_data: c_long):
|
|
|
if data_type == 1 and buf_size > 0:
|
|
|
pass
|
|
|
|
|
|
self._video_callback = CFUNCTYPE(
|
|
|
- None, c_long, c_ulong, POINTER(c_ubyte), c_ulong, c_long, c_ulong
|
|
|
+ None, c_long, c_uint32, POINTER(c_ubyte), c_uint32, c_long, c_long
|
|
|
)(video_callback)
|
|
|
|
|
|
self.sdk.CLIENT_SetVideoProcCallBack(self._video_callback, 0)
|