|
|
@@ -142,7 +142,14 @@ class DahuaSDK:
|
|
|
raise
|
|
|
|
|
|
def _setup_functions(self):
|
|
|
- """设置SDK函数签名"""
|
|
|
+ """设置SDK函数签名
|
|
|
+
|
|
|
+ 不同平台/版本的SDK可能缺少部分函数,用可选绑定处理:
|
|
|
+ - 必需函数:缺失则初始化失败
|
|
|
+ - 可选函数:缺失时设为None,运行时检查可用性
|
|
|
+ """
|
|
|
+
|
|
|
+ # === 必需函数 ===
|
|
|
|
|
|
# CLIENT_Init
|
|
|
self.sdk.CLIENT_Init.argtypes = [ctypes.c_void_p, c_ulong]
|
|
|
@@ -181,21 +188,32 @@ class DahuaSDK:
|
|
|
self.sdk.CLIENT_SnapPicture.argtypes = [c_long, POINTER(self.SNAP_PARAMS)]
|
|
|
self.sdk.CLIENT_SnapPicture.restype = ctypes.c_bool
|
|
|
|
|
|
- # CLIENT_SetSnapRevCallBack
|
|
|
- self.sdk.CLIENT_SetSnapRevCallBack.argtypes = [ctypes.c_void_p, c_ulong]
|
|
|
- self.sdk.CLIENT_SetSnapRevCallBack.restype = None
|
|
|
-
|
|
|
- # CLIENT_RealPlayEx - 扩展实时预览 (支持回调)
|
|
|
- self.sdk.CLIENT_RealPlayEx.argtypes = [c_long, c_int, ctypes.c_void_p, c_int]
|
|
|
- self.sdk.CLIENT_RealPlayEx.restype = c_long
|
|
|
-
|
|
|
- # CLIENT_StopRealPlayEx
|
|
|
- self.sdk.CLIENT_StopRealPlayEx.argtypes = [c_long]
|
|
|
- self.sdk.CLIENT_StopRealPlayEx.restype = ctypes.c_bool
|
|
|
+ # === 可选函数(某些SDK版本/平台可能缺失)===
|
|
|
+ self._optional_funcs = {}
|
|
|
|
|
|
- # CLIENT_SetVideoProcCallBack - 设置视频回调
|
|
|
- self.sdk.CLIENT_SetVideoProcCallBack.argtypes = [ctypes.c_void_p, c_ulong]
|
|
|
- self.sdk.CLIENT_SetVideoProcCallBack.restype = None
|
|
|
+ self._bind_optional('CLIENT_SetSnapRevCallBack', [ctypes.c_void_p, c_ulong], 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)
|
|
|
+
|
|
|
+ def _bind_optional(self, name: str, argtypes: list, restype):
|
|
|
+ """绑定可选SDK函数,缺失时设为None而不报错"""
|
|
|
+ try:
|
|
|
+ func = getattr(self.sdk, name, None)
|
|
|
+ if func is not None:
|
|
|
+ func.argtypes = argtypes
|
|
|
+ func.restype = restype
|
|
|
+ self._optional_funcs[name] = func
|
|
|
+ else:
|
|
|
+ self._optional_funcs[name] = None
|
|
|
+ print(f"SDK可选函数缺失: {name}")
|
|
|
+ except (AttributeError, OSError) as e:
|
|
|
+ self._optional_funcs[name] = None
|
|
|
+ print(f"SDK可选函数不可用: {name} - {e}")
|
|
|
+
|
|
|
+ def _has_func(self, name: str) -> bool:
|
|
|
+ """检查SDK函数是否可用"""
|
|
|
+ return self._optional_funcs.get(name) is not None
|
|
|
|
|
|
def init(self, disconnect_callback: Callable = None) -> bool:
|
|
|
"""
|
|
|
@@ -280,59 +298,38 @@ class DahuaSDK:
|
|
|
|
|
|
def real_play_ex(self, login_handle: int, channel: int = 0,
|
|
|
use_callback: bool = True) -> Optional[int]:
|
|
|
- """
|
|
|
- 开始实时预览 (扩展版,支持视频回调)
|
|
|
- Args:
|
|
|
- login_handle: 登录句柄
|
|
|
- channel: 通道号
|
|
|
- use_callback: 是否使用回调方式获取视频
|
|
|
- Returns:
|
|
|
- 预览句柄
|
|
|
- """
|
|
|
if not self.sdk or login_handle <= 0:
|
|
|
return None
|
|
|
|
|
|
- if use_callback:
|
|
|
- # 创建帧缓冲区
|
|
|
+ if not self._has_func('CLIENT_RealPlayEx'):
|
|
|
+ return self.real_play(login_handle, channel)
|
|
|
+
|
|
|
+ if use_callback and self._has_func('CLIENT_SetVideoProcCallBack'):
|
|
|
if channel not in self._video_frame_buffers:
|
|
|
self._video_frame_buffers[channel] = VideoFrameBuffer()
|
|
|
-
|
|
|
- # 设置视频回调
|
|
|
if self._video_callback is None:
|
|
|
self._setup_video_callback()
|
|
|
-
|
|
|
- # 使用 RealPlayEx 启动预览 (rType=0 表示标准预览)
|
|
|
play_handle = self.sdk.CLIENT_RealPlayEx(login_handle, channel, None, 0)
|
|
|
else:
|
|
|
- play_handle = self.sdk.CLIENT_RealPlay(login_handle, channel, None)
|
|
|
+ play_handle = self.sdk.CLIENT_RealPlayEx(login_handle, channel, None, 0) if self._has_func('CLIENT_RealPlayEx') else self.real_play(login_handle, channel)
|
|
|
|
|
|
return play_handle if play_handle > 0 else None
|
|
|
|
|
|
def _setup_video_callback(self):
|
|
|
- """设置视频数据回调函数"""
|
|
|
- # 视频回调函数签名:
|
|
|
- # void CALLBACK RealDataCallBackEx(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, LONG param, LDWORD dwUser)
|
|
|
+ if not self._has_func('CLIENT_SetVideoProcCallBack'):
|
|
|
+ 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):
|
|
|
- """视频数据回调 (内部函数)"""
|
|
|
- # data_type:
|
|
|
- # 0 = 系统头
|
|
|
- # 1 = 流数据 (P帧/I帧)
|
|
|
- # 2 = 音频数据
|
|
|
- # 3 = 智能信息
|
|
|
-
|
|
|
- if data_type == 1 and buf_size > 0: # 视频流数据
|
|
|
- # 注意: 这里收到的是编码后的视频流数据,需要解码才能得到图像
|
|
|
- # 实际解码需要使用 ffmpeg 或 SDK 的解码接口
|
|
|
+ if data_type == 1 and buf_size > 0:
|
|
|
pass
|
|
|
|
|
|
- # 创建 ctypes 回调并保存引用
|
|
|
self._video_callback = CFUNCTYPE(
|
|
|
None, c_long, c_ulong, POINTER(c_ubyte), c_ulong, c_long, c_ulong
|
|
|
)(video_callback)
|
|
|
|
|
|
- # 设置 SDK 回调
|
|
|
self.sdk.CLIENT_SetVideoProcCallBack(self._video_callback, 0)
|
|
|
|
|
|
def get_video_frame_buffer(self, channel: int = 0) -> Optional[VideoFrameBuffer]:
|