海康摄像头取流示例初始签入
This commit is contained in:
124
SHH.CameraSdk/Drivers/HikVision/HikErrorMapper.cs
Normal file
124
SHH.CameraSdk/Drivers/HikVision/HikErrorMapper.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [海康] 错误码映射器 (V3.3.1 修复版)
|
||||
/// <para>职责:将海康原始错误码映射为系统统一标准枚举,实现跨厂家故障语义归一化。</para>
|
||||
/// <para>协作:为 HikVideoSource.ReportError 提供标准化故障信息,支撑协调器自愈决策。</para>
|
||||
/// </summary>
|
||||
public static class HikErrorMapper
|
||||
{
|
||||
#region --- 静态映射资源 (Static Mapping Resources) ---
|
||||
|
||||
/// <summary>
|
||||
/// 海康原始错误码 → 系统标准错误码 映射表(只读,初始化后不可修改)
|
||||
/// </summary>
|
||||
private static readonly ReadOnlyDictionary<uint, CameraErrorCode> _codeMap;
|
||||
|
||||
/// <summary>
|
||||
/// 海康原始错误码 → 中文描述 映射表(只读,初始化后不可修改)
|
||||
/// </summary>
|
||||
private static readonly ReadOnlyDictionary<uint, string> _descMap;
|
||||
|
||||
/// <summary>
|
||||
/// 静态构造函数:初始化错误码映射表(程序启动时仅执行一次)
|
||||
/// </summary>
|
||||
static HikErrorMapper()
|
||||
{
|
||||
// 1. 初始化:海康原始错误码 → 系统标准错误码 映射
|
||||
var codeDict = new Dictionary<uint, CameraErrorCode>
|
||||
{
|
||||
{ 0, CameraErrorCode.Success },
|
||||
|
||||
// --- 基础环境相关错误 ---
|
||||
{ 3, CameraErrorCode.SdkNotInitialized }, // SDK未初始化 (对应 Bug S)
|
||||
{ 41, CameraErrorCode.LocalResourceError }, // 资源分配错误 (对应 Bug R)
|
||||
{ 121, CameraErrorCode.ComponentVersionMismatch }, // 动态库版本不匹配
|
||||
|
||||
// --- 网络通信相关错误 ---
|
||||
{ 7, CameraErrorCode.NetworkUnreachable }, // 连接设备失败(设备离线/网络不通)
|
||||
{ 10, CameraErrorCode.Timeout }, // 发送数据超时
|
||||
{ 11, CameraErrorCode.NetworkRecvError }, // 接收数据超时
|
||||
{ 73, CameraErrorCode.SocketError }, // Socket创建失败
|
||||
|
||||
// --- 身份认证相关错误 ---
|
||||
{ 1, CameraErrorCode.InvalidCredentials }, // 用户名或密码错误
|
||||
{ 2, CameraErrorCode.AccessDenied }, // 权限不足
|
||||
{ 47, CameraErrorCode.UserNotExist }, // 用户不存在
|
||||
{ 153, CameraErrorCode.AccountLocked }, // 用户名被锁定
|
||||
|
||||
// --- 设备资源相关错误 ---
|
||||
{ 4, CameraErrorCode.InvalidChannel }, // 通道号错误
|
||||
{ 5, CameraErrorCode.MaxConnectionsReached }, // 设备连接数超过最大限制 (对应 Bug W 幽灵登录后果)
|
||||
{ 23, CameraErrorCode.NotSupported }, // 设备不支持该功能
|
||||
|
||||
// --- 预览与播放相关错误 ---
|
||||
{ 18, CameraErrorCode.ChannelException }, // 设备通道处于错误状态
|
||||
{ 51, CameraErrorCode.PlayerSdkFailed }, // 调用播放库Player失败
|
||||
{ 105, CameraErrorCode.StreamTypeNotSupport } // 输入码流封装格式不支持
|
||||
};
|
||||
_codeMap = new ReadOnlyDictionary<uint, CameraErrorCode>(codeDict);
|
||||
|
||||
// 2. 初始化:海康原始错误码 → 中文描述 映射
|
||||
var descDict = new Dictionary<uint, string>
|
||||
{
|
||||
{ 0, "没有错误" },
|
||||
{ 1, "用户名或密码错误" },
|
||||
{ 2, "权限不足" },
|
||||
{ 3, "SDK未初始化" },
|
||||
{ 4, "通道号错误" },
|
||||
{ 5, "设备连接数超过最大" },
|
||||
{ 7, "连接设备失败(设备离线或网络不通)" },
|
||||
{ 9, "从设备接收数据失败" },
|
||||
{ 10, "向设备发送数据失败(超时)" },
|
||||
{ 11, "从设备接收数据失败(超时)" },
|
||||
{ 17, "参数错误" },
|
||||
{ 18, "设备通道处于错误状态" },
|
||||
{ 23, "设备不支持该功能" },
|
||||
{ 24, "设备忙" },
|
||||
{ 41, "SDK资源分配错误(内存不足)" },
|
||||
{ 43, "缓冲区已满" },
|
||||
{ 47, "用户不存在" },
|
||||
{ 51, "调用播放库Player失败" },
|
||||
{ 52, "登录设备用户数达到最大" },
|
||||
{ 55, "IP地址不匹配" },
|
||||
{ 56, "MAC地址不匹配" },
|
||||
{ 73, "Socket创建失败" },
|
||||
{ 105, "输入码流封装格式不支持" },
|
||||
{ 121, "动态库版本不匹配" },
|
||||
{ 153, "用户名被锁定" },
|
||||
};
|
||||
_descMap = new ReadOnlyDictionary<uint, string>(descDict);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 核心映射方法 (Core Mapping Methods) ---
|
||||
|
||||
/// <summary>
|
||||
/// 将海康原始错误码转换为系统统一标准错误码
|
||||
/// </summary>
|
||||
/// <param name="hikErrorCode">海康 SDK 返回的原始错误码</param>
|
||||
/// <returns>系统标准错误码(未匹配到时返回 CameraErrorCode.Unknown)</returns>
|
||||
public static CameraErrorCode Map(uint hikErrorCode)
|
||||
{
|
||||
// 尝试从映射表获取,未找到则返回未知错误
|
||||
return _codeMap.TryGetValue(hikErrorCode, out var code) ? code : CameraErrorCode.Unknown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取海康原始错误码的中文描述(含原始错误码)
|
||||
/// </summary>
|
||||
/// <param name="hikErrorCode">海康 SDK 返回的原始错误码</param>
|
||||
/// <returns>中文错误描述(格式:描述 (Code:原始错误码))</returns>
|
||||
public static string GetRawDescription(uint hikErrorCode)
|
||||
{
|
||||
if (_descMap.TryGetValue(hikErrorCode, out var desc))
|
||||
{
|
||||
return $"{desc} (Code:{hikErrorCode})";
|
||||
}
|
||||
// 未匹配到的错误码,返回默认描述
|
||||
return $"未知海康错误 (Code:{hikErrorCode})";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
37
SHH.CameraSdk/Drivers/HikVision/HikExtensions.cs
Normal file
37
SHH.CameraSdk/Drivers/HikVision/HikExtensions.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 海康 SDK 扩展方法类
|
||||
/// 功能:提供海康 API 调用结果校验的快捷扩展,简化错误处理逻辑
|
||||
/// </summary>
|
||||
public static class HikExtensions
|
||||
{
|
||||
#region --- 结果校验扩展 (Result Validation Extensions) ---
|
||||
|
||||
/// <summary>
|
||||
/// 校验海康 API 调用结果是否成功
|
||||
/// 功能:若结果为 false(调用失败),自动捕获海康错误码并抛出统一异常
|
||||
/// </summary>
|
||||
/// <param name="result">海康 API 调用返回的布尔结果(true=成功,false=失败)</param>
|
||||
/// <param name="actionName">操作名称(用于异常信息描述,如“设备登录”“启动预览”)</param>
|
||||
/// <param name="brand">设备品牌(默认海康威视,无需手动指定)</param>
|
||||
/// <exception cref="CameraException">API 调用失败时抛出,包含标准错误码与原始错误描述</exception>
|
||||
public static void EnsureSuccess(this bool result, string actionName, DeviceBrand brand = DeviceBrand.HikVision)
|
||||
{
|
||||
// 调用成功则直接返回,无需后续处理
|
||||
if (result) return;
|
||||
|
||||
// 1. 获取海康 SDK 最后一次操作的原始错误码
|
||||
uint lastError = HikNativeMethods.NET_DVR_GetLastError();
|
||||
|
||||
// 2. 将海康原始错误码映射为系统统一标准错误码
|
||||
CameraErrorCode standardCode = HikErrorMapper.Map(lastError);
|
||||
|
||||
// 3. 抛出统一异常,携带操作名称、标准错误码、原始错误描述等上下文
|
||||
throw new CameraException(standardCode, $"{actionName} 失败", brand, (int)lastError)
|
||||
.WithContext("Action", actionName) // 附加操作名称上下文
|
||||
.WithContext("HikDesc", HikErrorMapper.GetRawDescription(lastError)); // 附加海康原始错误描述
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
495
SHH.CameraSdk/Drivers/HikVision/HikNativeMethods.cs
Normal file
495
SHH.CameraSdk/Drivers/HikVision/HikNativeMethods.cs
Normal file
@@ -0,0 +1,495 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 海康 HCNetSDK.dll 原生方法封装(静态部分类)
|
||||
/// 功能:包含设备登录、预览、PTZ控制、异常回调等核心 SDK 接口定义
|
||||
/// 注意:所有 API 均直接映射海康原生 DLL 函数,参数顺序与类型需严格匹配官方文档
|
||||
/// </summary>
|
||||
public static partial class HikNativeMethods
|
||||
{
|
||||
#region --- 基础配置 (Basic Configuration) ---
|
||||
|
||||
/// <summary>
|
||||
/// HCNetSDK.dll 动态库路径
|
||||
/// 说明:确保项目中该路径与实际文件位置一致,否则会导致 DllImport 调用失败
|
||||
/// </summary>
|
||||
private const string DllName = "Drivers\\Hikvision\\HCNetSDK.dll";
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 结构体定义 (Structures) ---
|
||||
|
||||
/// <summary>
|
||||
/// 设备信息结构体 (NET_DEVICEINFO_V30)
|
||||
/// 功能:存储设备序列号、通道数、协议类型、能力集等核心信息
|
||||
/// 注:登录设备成功后通过 NET_DVR_Login_V30 接口返回
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NET_DEVICEINFO_V30
|
||||
{
|
||||
/// <summary> 设备序列号(长度48字节) </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
|
||||
public byte[] sSerialNumber;
|
||||
|
||||
/// <summary> 报警输入个数 </summary>
|
||||
public byte byAlarmInPortNum;
|
||||
|
||||
/// <summary> 报警输出个数 </summary>
|
||||
public byte byAlarmOutPortNum;
|
||||
|
||||
/// <summary> 硬盘个数 </summary>
|
||||
public byte byDiskNum;
|
||||
|
||||
/// <summary> 设备类型:1-DVR,2-ATM DVR,3-DVS 等 </summary>
|
||||
public byte byDVRType;
|
||||
|
||||
/// <summary> 模拟通道个数 </summary>
|
||||
public byte byChanNum;
|
||||
|
||||
/// <summary> 起始通道号(目前从1开始) </summary>
|
||||
public byte byStartChan;
|
||||
|
||||
/// <summary> 语音通道数 </summary>
|
||||
public byte byAudioChanNum;
|
||||
|
||||
/// <summary> 最大数字通道个数(低8位) </summary>
|
||||
public byte byIPChanNum;
|
||||
|
||||
/// <summary> 零通道编码个数 </summary>
|
||||
public byte byZeroChanNum;
|
||||
|
||||
/// <summary> 主码流传输协议类型:0-私有协议,1-RTSP,2-同时支持两者 </summary>
|
||||
public byte byMainProto;
|
||||
|
||||
/// <summary> 子码流传输协议类型:0-私有协议,1-RTSP,2-同时支持两者 </summary>
|
||||
public byte bySubProto;
|
||||
|
||||
/// <summary> 基础能力集(位掩码),位与结果为1表示支持对应功能 </summary>
|
||||
/// <remarks>
|
||||
/// bySupport & 0x1: 支持智能搜索<br/>
|
||||
/// bySupport & 0x2: 支持备份<br/>
|
||||
/// bySupport & 0x4: 支持压缩参数能力获取<br/>
|
||||
/// bySupport & 0x8: 支持多网卡<br/>
|
||||
/// bySupport & 0x10: 支持远程SADP<br/>
|
||||
/// bySupport & 0x20: 支持Raid卡功能<br/>
|
||||
/// bySupport & 0x40: 支持IPSAN目录查找<br/>
|
||||
/// bySupport & 0x80: 支持RTP over RTSP
|
||||
/// </remarks>
|
||||
public byte bySupport;
|
||||
|
||||
/// <summary> 能力集扩充(位掩码),位与结果为1表示支持对应功能 </summary>
|
||||
/// <remarks>
|
||||
/// bySupport1 & 0x1: 支持SNMP v30<br/>
|
||||
/// bySupport1 & 0x2: 支持区分回放和下载<br/>
|
||||
/// bySupport1 & 0x4: 支持布防优先级<br/>
|
||||
/// bySupport1 & 0x8: 智能设备支持布防时间段扩展<br/>
|
||||
/// bySupport1 & 0x10: 支持多磁盘数(超过33个)<br/>
|
||||
/// bySupport1 & 0x20: 支持RTSP over HTTP<br/>
|
||||
/// bySupport1 & 0x80: 支持车牌新报警信息(2012-9-28),且支持NET_DVR_IPPARACFG_V40结构体
|
||||
/// </remarks>
|
||||
public byte bySupport1;
|
||||
|
||||
/// <summary> 能力集扩充(位掩码),位与结果为1表示支持对应功能 </summary>
|
||||
/// <remarks>
|
||||
/// bySupport2 & 0x1: 解码器支持通过URL取流解码<br/>
|
||||
/// bySupport2 & 0x2: 支持FTP V40<br/>
|
||||
/// bySupport2 & 0x4: 支持ANR<br/>
|
||||
/// bySupport2 & 0x8: 支持CCD的通道参数配置<br/>
|
||||
/// bySupport2 & 0x10: 支持布防报警回传信息(仅抓拍机报警,新老报警结构)<br/>
|
||||
/// bySupport2 & 0x20: 支持单独获取设备状态子项<br/>
|
||||
/// bySupport2 & 0x40: 是码流加密设备
|
||||
/// </remarks>
|
||||
public byte bySupport2;
|
||||
|
||||
/// <summary> 设备型号 </summary>
|
||||
public ushort wDevType;
|
||||
|
||||
/// <summary> 能力集扩充(位掩码),位与结果为1表示支持对应功能 </summary>
|
||||
/// <remarks>
|
||||
/// bySupport3 & 0x1: 支持多码流<br/>
|
||||
/// bySupport3 & 0x4: 支持按组配置(通道图像参数、报警输入参数等)<br/>
|
||||
/// bySupport3 & 0x8: 支持TCP/UDP/多播预览的延时预览字段<br/>
|
||||
/// bySupport3 & 0x10: 支持获取报警主机主要状态(V40)<br/>
|
||||
/// bySupport3 & 0x20: 支持通过DDNS域名解析取流
|
||||
/// </remarks>
|
||||
public byte bySupport3;
|
||||
|
||||
/// <summary> 多码流支持标识(按位表示) </summary>
|
||||
/// <remarks>0-不支持,1-支持;bit1-码流3,bit2-码流4,bit7-主码流,bit8-子码流</remarks>
|
||||
public byte byMultiStreamProto;
|
||||
|
||||
/// <summary> 起始数字通道号(0表示无效) </summary>
|
||||
public byte byStartDChan;
|
||||
|
||||
/// <summary> 起始数字对讲通道号(0表示无效) </summary>
|
||||
public byte byStartDTalkChan;
|
||||
|
||||
/// <summary> 数字通道个数(高8位) </summary>
|
||||
public byte byHighDChanNum;
|
||||
|
||||
/// <summary> 能力集扩充(位掩码),位与结果为1表示支持对应功能 </summary>
|
||||
public byte bySupport4;
|
||||
|
||||
/// <summary> 支持语种能力(按位表示) </summary>
|
||||
/// <remarks>
|
||||
/// byLanguageType = 0: 老设备<br/>
|
||||
/// byLanguageType & 0x1: 支持中文<br/>
|
||||
/// byLanguageType & 0x2: 支持英文
|
||||
/// </remarks>
|
||||
public byte byLanguageType;
|
||||
|
||||
/// <summary> 音频输入通道数 </summary>
|
||||
public byte byVoiceInChanNum;
|
||||
|
||||
/// <summary> 音频输入起始通道号 </summary>
|
||||
public byte byStartVoiceInChanNo;
|
||||
|
||||
/// <summary> 保留字段(必须置0) </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
public byte[] byRes3;
|
||||
|
||||
/// <summary> AES算法加密/解密能力 </summary>
|
||||
public byte byMirrorCap;
|
||||
|
||||
/// <summary> 起始数字通道号(扩展) </summary>
|
||||
public ushort wStartIPChanNo;
|
||||
|
||||
/// <summary> 保留字段(必须置0) </summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
|
||||
public byte[] byRes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预览参数结构体 (NET_DVR_PREVIEWINFO)
|
||||
/// 功能:配置实时预览的通道、码流类型、连接方式等参数
|
||||
/// 注:用于 NET_DVR_RealPlay_V40 接口的输入参数
|
||||
/// </summary>
|
||||
[StructLayoutAttribute(LayoutKind.Sequential)]
|
||||
public struct NET_DVR_PREVIEWINFO
|
||||
{
|
||||
/// <summary> 通道号(模拟通道从1开始) </summary>
|
||||
public Int32 lChannel;
|
||||
|
||||
/// <summary> 码流类型:0-主码流,1-子码流,2-码流3,3-码流4,以此类推 </summary>
|
||||
public uint dwStreamType;
|
||||
|
||||
/// <summary> 连接方式:0-TCP,1-UDP,2-多播,3-RTP,4-RTP/RTSP,5-RTSP/HTTP </summary>
|
||||
public uint dwLinkMode;
|
||||
|
||||
/// <summary> 播放窗口句柄 </summary>
|
||||
/// <remarks>IntPtr.Zero 表示不让 SDK 直接渲染,仅获取原始流数据</remarks>
|
||||
public IntPtr hPlayWnd;
|
||||
|
||||
/// <summary> 取流模式:0-非阻塞,1-阻塞(阻塞模式超时5秒返回) </summary>
|
||||
/// <remarks>阻塞模式不适合轮询取流操作</remarks>
|
||||
public bool bBlocked;
|
||||
|
||||
/// <summary> 是否启用回放录像:0-不启用,1-启用 </summary>
|
||||
public bool bPassbackRecord;
|
||||
|
||||
/// <summary> 预览模式:0-正常预览,1-延迟预览 </summary>
|
||||
public byte byPreviewMode;
|
||||
|
||||
/// <summary> 流ID(lChannel为0xffffffff时启用,长度32字节) </summary>
|
||||
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = STREAM_ID_LEN, ArraySubType = UnmanagedType.I1)]
|
||||
public byte[] byStreamID;
|
||||
|
||||
/// <summary> 应用层协议类型:0-私有协议,1-RTSP协议 </summary>
|
||||
public byte byProtoType;
|
||||
|
||||
/// <summary> 保留字段(必须置0) </summary>
|
||||
public byte byRes1;
|
||||
|
||||
/// <summary> 码流编解码类型:0-通用编码数据,1-热成像原始数据(含温度加密信息) </summary>
|
||||
public byte byVideoCodingType;
|
||||
|
||||
/// <summary> 播放库最大缓冲帧数(范围1-50,0表示默认1帧) </summary>
|
||||
public uint dwDisplayBufNum;
|
||||
|
||||
/// <summary> NPQ模式:0-直连,1-过流媒体 </summary>
|
||||
public byte byNPQMode;
|
||||
|
||||
/// <summary> 保留字段(必须置0) </summary>
|
||||
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 215, ArraySubType = UnmanagedType.I1)]
|
||||
public byte[] byRes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间结构体 (NET_DVR_TIME)
|
||||
/// [Fix Bug P: 结构体炸弹] 修复结构体对齐问题,避免栈内存覆盖导致随机崩溃
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 原问题:ushort/byte 混合定义导致结构体总大小不足16字节,SDK写入时覆盖栈变量<br/>
|
||||
/// 修复方案:使用 Pack=4 对齐,成员类型统一为 uint(4字节),与 C++ DWORD 匹配
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct NET_DVR_TIME
|
||||
{
|
||||
public uint dwYear; // 年份
|
||||
public uint dwMonth; // 月份(1-12)
|
||||
public uint dwDay; // 日期(1-31)
|
||||
public uint dwHour; // 小时(0-23)
|
||||
public uint dwMinute; // 分钟(0-59)
|
||||
public uint dwSecond; // 秒(0-59)
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 常量定义 (Constants) ---
|
||||
|
||||
/// <summary> 流ID长度(32字节) </summary>
|
||||
public const int STREAM_ID_LEN = 32;
|
||||
|
||||
/// <summary> 数据类型常量:系统头数据 </summary>
|
||||
public const int NET_DVR_SYSHEAD = 1;
|
||||
|
||||
/// <summary> 数据类型常量:视频流数据(H.264/H.265) </summary>
|
||||
public const int NET_DVR_STREAMDATA = 2;
|
||||
|
||||
/// <summary> 数据类型常量:音频数据 </summary>
|
||||
public const int NET_DVR_AUDIOSTREAMDATA = 3;
|
||||
|
||||
/// <summary> 命令常量:获取时间配置 </summary>
|
||||
public const uint NET_DVR_GET_TIMECFG = 118;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- PTZ 控制相关 (PTZ Control) ---
|
||||
|
||||
/// <summary> PTZ命令常量:镜头控制 </summary>
|
||||
public const uint ZOOM_IN = 11; // 焦距变大(拉近)
|
||||
public const uint ZOOM_OUT = 12; // 焦距变小(拉远)
|
||||
public const uint FOCUS_NEAR = 13; // 焦点前调
|
||||
public const uint FOCUS_FAR = 14; // 焦点后调
|
||||
public const uint IRIS_OPEN = 15; // 光圈扩大
|
||||
public const uint IRIS_CLOSE = 16; // 光圈缩小
|
||||
|
||||
/// <summary> PTZ命令常量:方向控制 </summary>
|
||||
public const uint TILT_UP = 21; // 云台上仰
|
||||
public const uint TILT_DOWN = 22; // 云台下俯
|
||||
public const uint PAN_LEFT = 23; // 云台左转
|
||||
public const uint PAN_RIGHT = 24; // 云台右转
|
||||
public const uint UP_LEFT = 25; // 上左移动
|
||||
public const uint UP_RIGHT = 26; // 上右移动
|
||||
public const uint DOWN_LEFT = 27; // 下左移动
|
||||
public const uint DOWN_RIGHT = 28; // 下右移动
|
||||
public const uint PAN_AUTO = 29; // 自动扫描
|
||||
|
||||
/// <summary> PTZ命令常量:辅助功能 </summary>
|
||||
public const uint LIGHT_PWRON = 2; // 接通灯光电源
|
||||
public const uint WIPER_PWRON = 3; // 接通雨刷开关
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 异常回调相关 (Exception Callback) ---
|
||||
|
||||
/// <summary> 异常类型常量 </summary>
|
||||
public const int EXCEPTION_EXCHANGE = 0x8000; // 用户交互时异常
|
||||
public const int EXCEPTION_AUDIOEXCHANGE = 0x8001; // 语音对讲异常
|
||||
public const int EXCEPTION_ALARM = 0x8002; // 报警异常
|
||||
public const int EXCEPTION_PREVIEW = 0x8003; // 网络预览异常
|
||||
public const int EXCEPTION_SERIAL = 0x8004; // 透明通道异常
|
||||
public const int EXCEPTION_RECONNECT = 0x8005; // 预览时重连成功
|
||||
|
||||
/// <summary>
|
||||
/// 异常消息回调委托
|
||||
/// 功能:SDK 发生异常时触发,返回异常类型、用户ID、相关句柄等信息
|
||||
/// </summary>
|
||||
/// <param name="dwType">异常类型(对应 EXCEPTION_XXX 常量)</param>
|
||||
/// <param name="lUserID">用户ID(NET_DVR_Login_V30 返回值)</param>
|
||||
/// <param name="lHandle">异常关联句柄(预览句柄/报警句柄等)</param>
|
||||
/// <param name="pUser">用户自定义数据指针</param>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate void EXCEPTION_CALLBACK(uint dwType, int lUserID, int lHandle, IntPtr pUser);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 预览回调相关 (Preview Callback) ---
|
||||
|
||||
/// <summary>
|
||||
/// 预览数据回调委托
|
||||
/// 功能:实时预览时触发,返回原始流数据(系统头/视频流/音频流)
|
||||
/// </summary>
|
||||
/// <param name="lRealHandle">预览句柄(NET_DVR_RealPlay_V40 返回值)</param>
|
||||
/// <param name="dwDataType">数据类型(对应 NET_DVR_XXX 数据类型常量)</param>
|
||||
/// <param name="pBuffer">数据缓冲区指针</param>
|
||||
/// <param name="dwBufSize">缓冲区大小(字节)</param>
|
||||
/// <param name="pUser">用户自定义数据指针</param>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate void REALDATACALLBACK(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- SDK 基础接口 (Basic SDK Interfaces) ---
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 SDK
|
||||
/// 功能:调用所有其他 SDK 接口的前提,必须先初始化再使用
|
||||
/// </summary>
|
||||
/// <returns>初始化成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_Init();
|
||||
|
||||
/// <summary>
|
||||
/// 释放 SDK 资源
|
||||
/// 功能:程序退出前调用,释放 SDK 占用的非托管资源(网络连接、内存等)
|
||||
/// </summary>
|
||||
/// <returns>释放成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_Cleanup();
|
||||
|
||||
/// <summary>
|
||||
/// 获取最后一次操作的错误码
|
||||
/// 功能:API 调用失败后,通过此接口获取具体错误原因
|
||||
/// </summary>
|
||||
/// <returns>错误码(需结合海康官方文档查询含义)</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern uint NET_DVR_GetLastError();
|
||||
|
||||
/// <summary>
|
||||
/// 设置网络连接超时时间和连接尝试次数
|
||||
/// </summary>
|
||||
/// <param name="dwWaitTime">超时时间(毫秒),推荐 3000ms</param>
|
||||
/// <param name="dwTryTimes">连接尝试次数,推荐 1 次</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_SetConnectTime(uint dwWaitTime, uint dwTryTimes);
|
||||
|
||||
/// <summary>
|
||||
/// 设置自动重连功能
|
||||
/// </summary>
|
||||
/// <param name="dwInterval">重连间隔(毫秒),推荐 10000ms</param>
|
||||
/// <param name="bEnableRecon">是否启用重连:0-禁用,1-启用</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_SetReconnect(uint dwInterval, bool bEnableRecon);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 设备登录/登出接口 (Device Login/Logout) ---
|
||||
|
||||
/// <summary>
|
||||
/// 用户注册设备(登录)
|
||||
/// 功能:建立与设备的连接,获取用户ID(后续接口调用的核心标识)
|
||||
/// </summary>
|
||||
/// <param name="sDVRIP">设备IP地址</param>
|
||||
/// <param name="wDVRPort">设备端口号(海康默认8000)</param>
|
||||
/// <param name="sUserName">登录用户名(默认 admin)</param>
|
||||
/// <param name="sPassword">登录密码</param>
|
||||
/// <param name="lpDeviceInfo">输出参数:设备信息结构体</param>
|
||||
/// <returns>登录成功返回用户ID(非负整数),失败返回 -1</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern int NET_DVR_Login_V30(string sDVRIP, Int32 wDVRPort, string sUserName, string sPassword, ref NET_DEVICEINFO_V30 lpDeviceInfo);
|
||||
|
||||
/// <summary>
|
||||
/// 用户注销(登出)
|
||||
/// 功能:断开与设备的连接,释放用户ID关联的资源
|
||||
/// </summary>
|
||||
/// <param name="lUserID">用户ID(NET_DVR_Login_V30 返回值)</param>
|
||||
/// <returns>登出成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_Logout(int lUserID);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 预览控制接口 (Preview Control) ---
|
||||
|
||||
/// <summary>
|
||||
/// 实时预览(V40版本,支持回调)
|
||||
/// 功能:启动设备实时取流,通过回调获取原始流数据
|
||||
/// </summary>
|
||||
/// <param name="lUserID">用户ID(NET_DVR_Login_V30 返回值)</param>
|
||||
/// <param name="lpPreviewInfo">预览参数结构体</param>
|
||||
/// <param name="fRealDataCallBack_V30">流数据回调函数</param>
|
||||
/// <param name="pUser">用户自定义数据指针</param>
|
||||
/// <returns>预览成功返回预览句柄(非负整数),失败返回 -1</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern int NET_DVR_RealPlay_V40(int lUserID, ref NET_DVR_PREVIEWINFO lpPreviewInfo, REALDATACALLBACK fRealDataCallBack_V30, IntPtr pUser);
|
||||
|
||||
/// <summary>
|
||||
/// 停止预览
|
||||
/// 功能:停止实时取流,释放预览句柄关联的资源
|
||||
/// </summary>
|
||||
/// <param name="lRealHandle">预览句柄(NET_DVR_RealPlay_V40 返回值)</param>
|
||||
/// <returns>停止成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_StopRealPlay(int lRealHandle);
|
||||
|
||||
/// <summary>
|
||||
/// 强制生成I帧
|
||||
/// 功能:主动触发设备发送I帧,优化视频流解码延时
|
||||
/// </summary>
|
||||
/// <param name="lUserID">用户ID</param>
|
||||
/// <param name="lChannel">通道号</param>
|
||||
/// <returns>操作成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_MakeKeyFrame(int lUserID, int lChannel);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- PTZ 控制接口 (PTZ Control Interfaces) ---
|
||||
|
||||
/// <summary>
|
||||
/// 云台控制(带速度)
|
||||
/// 功能:控制云台旋转、镜头缩放、光圈调节等操作
|
||||
/// </summary>
|
||||
/// <param name="lUserID">用户ID</param>
|
||||
/// <param name="lChannel">通道号</param>
|
||||
/// <param name="dwPTZCommand">PTZ控制命令(对应 PTZ 命令常量)</param>
|
||||
/// <param name="dwStop">启停标识:0-开始,1-停止</param>
|
||||
/// <param name="dwSpeed">控制速度(1-7,数值越大速度越快)</param>
|
||||
/// <returns>操作成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_PTZControlWithSpeed_Other(int lUserID, int lChannel, uint dwPTZCommand, uint dwStop, uint dwSpeed);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 异常回调接口 (Exception Callback Interfaces) ---
|
||||
|
||||
/// <summary>
|
||||
/// 设置连接超时时间和重连策略(兼容旧版本)
|
||||
/// </summary>
|
||||
/// <param name="dwInterval">重连间隔(毫秒),建议 3000</param>
|
||||
/// <param name="bEnableRecon">是否启用重连:1-启用,0-禁用</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_SetReconnect(uint dwInterval, int bEnableRecon);
|
||||
|
||||
/// <summary>
|
||||
/// 注册异常、重连等消息的回调函数
|
||||
/// 功能:绑定异常回调委托,接收 SDK 层面的异常通知
|
||||
/// </summary>
|
||||
/// <param name="nMessage">消息类型(0 表示所有消息)</param>
|
||||
/// <param name="hWnd">窗口句柄(可为 IntPtr.Zero)</param>
|
||||
/// <param name="fExceptionCallBack">异常回调函数委托</param>
|
||||
/// <param name="pUser">用户自定义数据指针</param>
|
||||
/// <returns>注册成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool NET_DVR_SetExceptionCallBack_V30(uint nMessage, IntPtr hWnd, EXCEPTION_CALLBACK fExceptionCallBack, IntPtr pUser);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 通用配置接口 (General Configuration Interfaces) ---
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备配置
|
||||
/// 功能:通用接口,根据命令号获取设备特定配置(如时间配置、通道参数等)
|
||||
/// </summary>
|
||||
/// <param name="lUserID">用户ID</param>
|
||||
/// <param name="dwCommand">配置命令号(如 NET_DVR_GET_TIMECFG)</param>
|
||||
/// <param name="lChannel">通道号(-1 表示设备级配置)</param>
|
||||
/// <param name="lpOutBuffer">输出缓冲区指针(存储配置数据)</param>
|
||||
/// <param name="dwOutBufferSize">输出缓冲区大小(字节)</param>
|
||||
/// <param name="lpBytesReturned">输出参数:实际返回的数据大小(字节)</param>
|
||||
/// <returns>获取成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern bool NET_DVR_GetDVRConfig(
|
||||
int lUserID,
|
||||
uint dwCommand,
|
||||
int lChannel,
|
||||
IntPtr lpOutBuffer,
|
||||
uint dwOutBufferSize,
|
||||
ref uint lpBytesReturned);
|
||||
|
||||
#endregion
|
||||
}
|
||||
347
SHH.CameraSdk/Drivers/HikVision/HikPlayMethods.cs
Normal file
347
SHH.CameraSdk/Drivers/HikVision/HikPlayMethods.cs
Normal file
@@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 海康播放库 PlayCtrl.dll 的封装
|
||||
/// 完全参考官方 WinPlayCtrl.cs 定义,提供解码、播放、端口管理等核心能力
|
||||
/// </summary>
|
||||
public static class HikPlayMethods
|
||||
{
|
||||
#region --- 基础配置 (Basic Configuration) ---
|
||||
|
||||
/// <summary>
|
||||
/// PlayCtrl.dll 动态库路径
|
||||
/// 注意:请确保项目中该路径与实际文件位置一致,否则会导致 DllImport 失败
|
||||
/// </summary>
|
||||
private const string DllName = @"Drivers\\Hikvision\\PlayCtrl.dll";
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 常量定义 (Constants) ---
|
||||
|
||||
/// <summary> 最大支持的通道数 </summary>
|
||||
public const int PLAYM4_MAX_SUPPORTS = 500;
|
||||
|
||||
// 流模式常量
|
||||
public const int STREAME_REALTIME = 0; // 实时流模式
|
||||
public const int STREAME_FILE = 1; // 文件流模式
|
||||
|
||||
// 帧类型常量(音频/视频)
|
||||
public const int T_AUDIO16 = 101; // 16位音频帧
|
||||
public const int T_AUDIO8 = 100; // 8位音频帧
|
||||
public const int T_UYVY = 1; // UYVY格式视频帧
|
||||
public const int T_YV12 = 3; // YV12格式视频帧(常用)
|
||||
public const int T_RGB32 = 7; // RGB32格式视频帧
|
||||
|
||||
// 显示缓冲区大小常量
|
||||
public const int MAX_DIS_FRAMES = 50; // 最大显示缓冲帧数
|
||||
public const int MIN_DIS_FRAMES = 1; // 最小显示缓冲帧数
|
||||
|
||||
// 源缓冲区大小常量(单位:字节)
|
||||
public const int SOURCE_BUF_MAX = 1024 * 100000; // 最大源缓冲区(100MB)
|
||||
public const int SOURCE_BUF_MIN = 1024 * 50; // 最小源缓冲区(50KB)
|
||||
|
||||
// 错误码常量(PlayCtrl.dll 返回错误标识)
|
||||
public const int PLAYM4_NOERROR = 0; // 无错误
|
||||
public const int PLAYM4_PARA_OVER = 1; // 参数超出范围
|
||||
public const int PLAYM4_ORDER_ERROR = 2; // 函数调用顺序错误
|
||||
public const int PLAYM4_TIMER_ERROR = 3; // 定时器初始化错误
|
||||
public const int PLAYM4_DEC_VIDEO_ERROR = 4; // 视频解码错误
|
||||
public const int PLAYM4_DEC_AUDIO_ERROR = 5; // 音频解码错误
|
||||
public const int PLAYM4_ALLOC_MEMORY_ERROR = 6; // 内存分配错误
|
||||
public const int PLAYM4_OPEN_FILE_ERROR = 7; // 打开文件错误
|
||||
public const int PLAYM4_CREATE_OBJ_ERROR = 8; // 创建对象错误
|
||||
public const int PLAYM4_CREATE_DDRAW_ERROR = 9; // 创建DirectDraw错误
|
||||
public const int PLAYM4_CREATE_OFFSCREEN_ERROR = 10;// 创建离屏表面错误
|
||||
public const int PLAYM4_BUF_OVER = 11; // 缓冲区溢出
|
||||
public const int PLAYM4_CREATE_SOUND_ERROR = 12; // 创建音频设备错误
|
||||
public const int PLAYM4_SET_VOLUME_ERROR = 13; // 设置音量错误
|
||||
public const int PLAYM4_SUPPORT_FILE_ONLY = 14; // 仅支持文件流
|
||||
public const int PLAYM4_SUPPORT_STREAM_ONLY = 15; // 仅支持实时流
|
||||
public const int PLAYM4_SYS_NOT_SUPPORT = 16; // 系统不支持该功能
|
||||
public const int PLAYM4_FILEHEADER_UNKNOWN = 17; // 文件头格式未知
|
||||
public const int PLAYM4_VERSION_INCORRECT = 18; // 版本不匹配
|
||||
public const int PLAYM4_INIT_DECODER_ERROR = 19; // 解码器初始化错误
|
||||
public const int PLAYM4_CHECK_FILE_ERROR = 20; // 校验文件错误
|
||||
public const int PLAYM4_INIT_TIMER_ERROR = 21; // 初始化定时器错误
|
||||
public const int PLAYM4_BLT_ERROR = 22; // 图像绘制错误
|
||||
public const int PLAYM4_UPDATE_ERROR = 23; // 更新显示错误
|
||||
|
||||
// PTZ控制命令常量(保留自定义逻辑,官方示例未包含)
|
||||
public const uint ZOOM_IN = 11; // 焦距变大(拉近)
|
||||
public const uint ZOOM_OUT = 12; // 焦距变小(拉远)
|
||||
public const uint FOCUS_NEAR = 13; // 焦点前调
|
||||
public const uint FOCUS_FAR = 14; // 焦点后调
|
||||
public const uint IRIS_OPEN = 15; // 光圈扩大
|
||||
public const uint IRIS_CLOSE = 16; // 光圈缩小
|
||||
public const uint TILT_UP = 21; // 云台上仰
|
||||
public const uint TILT_DOWN = 22; // 云台下俯
|
||||
public const uint PAN_LEFT = 23; // 云台左转
|
||||
public const uint PAN_RIGHT = 24; // 云台右转
|
||||
public const uint UP_LEFT = 25; // 上左移动
|
||||
public const uint UP_RIGHT = 26; // 上右移动
|
||||
public const uint DOWN_LEFT = 27; // 下左移动
|
||||
public const uint DOWN_RIGHT = 28; // 下右移动
|
||||
public const uint PAN_AUTO = 29; // 云台自动扫描
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 结构体定义 (Structs) ---
|
||||
|
||||
/// <summary>
|
||||
/// 帧信息结构体:存储解码后帧的宽高、帧率、序号等关键信息
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FRAME_INFO
|
||||
{
|
||||
public int nWidth; // 帧宽度(像素)
|
||||
public int nHeight; // 帧高度(像素)
|
||||
public int nStamp; // 时间戳
|
||||
public int nType; // 帧类型(对应 T_XXX 常量)
|
||||
public int nFrameRate; // 帧率(fps)
|
||||
public uint dwFrameNum; // 帧序号
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 帧位置结构体:存储文件流中帧的位置、时间等信息
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FRAME_POS
|
||||
{
|
||||
public int nFilePos; // 文件中的位置偏移
|
||||
public int nFrameNum; // 帧序号
|
||||
public int nFrameTime; // 帧时间(毫秒)
|
||||
public int nErrorFrameNum; // 错误帧数
|
||||
public IntPtr pErrorTime; // 错误时间数组指针
|
||||
public int nErrorLostFrameNum; // 丢失的错误帧数
|
||||
public int nErrorFrameSize; // 错误帧大小
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 帧类型结构体:存储帧数据缓冲区、大小等信息
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FRAME_TYPE
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPStr)]
|
||||
public string pDataBuf; // 帧数据缓冲区指针
|
||||
public int nSize; // 缓冲区大小(字节)
|
||||
public int nFrameNum; // 帧序号
|
||||
public bool bIsAudio; // 是否为音频帧
|
||||
public int nReserved; // 保留字段(置0)
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 委托定义 (Delegates) ---
|
||||
|
||||
/// <summary>
|
||||
/// 解码回调委托 (对应官方 DECCBFUN)
|
||||
/// 功能:解码完成后触发,返回解码后的帧数据
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="pBuf">解码后数据缓冲区指针</param>
|
||||
/// <param name="nSize">缓冲区大小(字节)</param>
|
||||
/// <param name="pFrameInfo">帧信息结构体(引用传递)</param>
|
||||
/// <param name="nReserved1">保留字段1(置0)</param>
|
||||
/// <param name="nReserved2">保留字段2(置0)</param>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate void DECCBFUN(
|
||||
int nPort,
|
||||
IntPtr pBuf,
|
||||
int nSize,
|
||||
ref FRAME_INFO pFrameInfo,
|
||||
int nReserved1,
|
||||
int nReserved2
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 显示回调委托 (对应官方 DISPLAYCBFUN)
|
||||
/// 功能:帧数据准备显示时触发,用于自定义渲染逻辑
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="pBuf">显示数据缓冲区指针</param>
|
||||
/// <param name="nSize">缓冲区大小(字节)</param>
|
||||
/// <param name="nWidth">显示宽度(像素)</param>
|
||||
/// <param name="nHeight">显示高度(像素)</param>
|
||||
/// <param name="nStamp">时间戳</param>
|
||||
/// <param name="nType">数据类型(对应 T_XXX 常量)</param>
|
||||
/// <param name="nReserved">保留字段(置0)</param>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate void DISPLAYCBFUN(
|
||||
int nPort,
|
||||
IntPtr pBuf,
|
||||
int nSize,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
int nStamp,
|
||||
int nType,
|
||||
int nReserved
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 文件结束回调委托
|
||||
/// 功能:文件流播放完成时触发
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="pUser">用户自定义数据指针</param>
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
public delegate void FILEENDCALLBACK(int nPort, IntPtr pUser);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- API 导入 (Dll Imports) ---
|
||||
|
||||
/// <summary>
|
||||
/// 获取闲置的播放端口。
|
||||
/// <para>[警告] 此函数非线程安全,且端口资源有限(最多500个)。</para>
|
||||
/// <para>高并发场景下必须加全局锁,防止端口分配冲突。</para>
|
||||
/// </summary>
|
||||
/// <param name="nPort">输出参数:获取到的闲置端口号(输出-1表示失败)</param>
|
||||
/// <returns>获取成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_GetPort(ref int nPort);
|
||||
|
||||
/// <summary>
|
||||
/// 释放播放端口
|
||||
/// 功能:不再使用端口时调用,避免端口资源泄漏
|
||||
/// </summary>
|
||||
/// <param name="nPort">要释放的端口号</param>
|
||||
/// <returns>释放成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_FreePort(int nPort);
|
||||
|
||||
/// <summary>
|
||||
/// 打开流
|
||||
/// 功能:初始化端口的流缓冲区,准备接收流数据
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="pFileHeadBuf">文件头数据缓冲区指针(实时流可为空)</param>
|
||||
/// <param name="nSize">缓冲区大小(字节)</param>
|
||||
/// <param name="nBufPoolSize">流缓冲区池大小(字节)</param>
|
||||
/// <returns>打开成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_OpenStream(int nPort, IntPtr pFileHeadBuf, uint nSize, uint nBufPoolSize);
|
||||
|
||||
/// <summary>
|
||||
/// 关闭流
|
||||
/// 功能:停止接收流数据,释放流缓冲区资源
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <returns>关闭成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_CloseStream(int nPort);
|
||||
|
||||
/// <summary>
|
||||
/// 开始播放
|
||||
/// 功能:启动解码和显示流程,绑定到指定窗口句柄
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="hWnd">显示窗口句柄(IntPtr.Zero 表示不绑定窗口)</param>
|
||||
/// <returns>启动成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_Play(int nPort, IntPtr hWnd);
|
||||
|
||||
/// <summary>
|
||||
/// 停止播放
|
||||
/// 功能:停止解码和显示,释放播放相关资源
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <returns>停止成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_Stop(int nPort);
|
||||
|
||||
/// <summary>
|
||||
/// 输入流数据
|
||||
/// 功能:向播放端口推送原始流数据,供解码使用
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="pBuf">流数据缓冲区指针</param>
|
||||
/// <param name="nSize">数据大小(字节)</param>
|
||||
/// <returns>输入成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_InputData(int nPort, IntPtr pBuf, uint nSize);
|
||||
|
||||
/// <summary>
|
||||
/// 设置解码回调 (Ex版本)
|
||||
/// 功能:绑定解码完成后的回调函数,用于获取解码后的帧数据
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="DecCBFun">解码回调函数委托</param>
|
||||
/// <param name="pDest">目标缓冲区指针(可为空)</param>
|
||||
/// <param name="nDestSize">目标缓冲区大小(字节)</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_SetDecCallBackEx(int nPort, DECCBFUN DecCBFun, IntPtr pDest, int nDestSize);
|
||||
|
||||
/// <summary>
|
||||
/// 设置流打开模式
|
||||
/// 功能:指定端口接收的流类型(实时流/文件流)
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="nMode">流模式(对应 STREAME_XXX 常量)</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_SetStreamOpenMode(int nPort, uint nMode);
|
||||
|
||||
/// <summary>
|
||||
/// 设置显示缓冲区数量
|
||||
/// 功能:调整显示缓冲帧数,平衡流畅度与延迟
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="nNum">缓冲帧数(范围:MIN_DIS_FRAMES ~ MAX_DIS_FRAMES)</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_SetDisplayBuf(int nPort, uint nNum);
|
||||
|
||||
/// <summary>
|
||||
/// 设置叠加模式
|
||||
/// 功能:配置图像叠加方式及透明色
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="bOverlay">是否启用叠加(1=启用,0=禁用)</param>
|
||||
/// <param name="colorKey">透明色键值</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_SetOverlayMode(int nPort, int bOverlay, uint colorKey);
|
||||
|
||||
/// <summary>
|
||||
/// 获取最后一次错误码
|
||||
/// 功能:API 调用失败后,获取具体错误原因(对应 PLAYM4_XXX 错误常量)
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <returns>错误码(0 表示无错误)</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern uint PlayM4_GetLastError(int nPort);
|
||||
|
||||
/// <summary>
|
||||
/// 设置视频解码模式
|
||||
/// 功能:切换硬解码/软解码模式(补充:用于硬件加速优化)
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <param name="nMode">解码模式(0=软解码,1=硬解码,具体值参考官方文档)</param>
|
||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_SetDecVideoMode(int nPort, int nMode);
|
||||
|
||||
/// <summary>
|
||||
/// 获取源缓冲区剩余空间
|
||||
/// 功能:查询端口流缓冲区的剩余可用空间(字节)
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <returns>剩余空间大小(字节)</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern uint PlayM4_GetSourceBufferRemain(int nPort);
|
||||
|
||||
/// <summary>
|
||||
/// 重置源缓冲区
|
||||
/// 功能:清空端口流缓冲区中的未解码数据
|
||||
/// </summary>
|
||||
/// <param name="nPort">播放端口号</param>
|
||||
/// <returns>重置成功返回 true,失败返回 false</returns>
|
||||
[DllImport(DllName)]
|
||||
public static extern bool PlayM4_ResetSourceBuffer(int nPort);
|
||||
|
||||
#endregion
|
||||
}
|
||||
125
SHH.CameraSdk/Drivers/HikVision/HikSdkManager.cs
Normal file
125
SHH.CameraSdk/Drivers/HikVision/HikSdkManager.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [驱动支持层] 海康 SDK 全局资源管理器 (V3.3.1 修复版)
|
||||
/// <para>核心修复:</para>
|
||||
/// <para>1. [Bug S] 引用计数保护:增加下溢检测,防止异常销毁流程导致的计数器错乱。</para>
|
||||
/// <para>2. [Bug C] 禁用内部重连:确保 SDK 不会背着上层偷偷重连,彻底消除僵尸连接。</para>
|
||||
/// </summary>
|
||||
public static class HikSdkManager
|
||||
{
|
||||
#region --- 全局状态与锁 (Global States & Locks) ---
|
||||
|
||||
/// <summary>
|
||||
/// 全局引用计数器。
|
||||
/// 只有当计数从 0 变 1 时才进行物理初始化,从 1 变 0 时才物理卸载。
|
||||
/// </summary>
|
||||
private static int _referenceCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 静态同步锁。
|
||||
/// 用于保护 _referenceCount 的原子操作,防止多线程并发 Start/Stop 时导致的初始化冲突。
|
||||
/// </summary>
|
||||
private static readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// 播放库预热状态标记。
|
||||
/// 用于避免重复执行硬件探测(首次预热后后续直接返回)。
|
||||
/// </summary>
|
||||
private static bool _isWarmedUp = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- SDK 初始化与卸载 (SDK Initialization & Uninstallation) ---
|
||||
|
||||
/// <summary>
|
||||
/// 全局初始化海康 SDK 环境。
|
||||
/// <para>此方法是幂等的,内部会自动增加引用计数,支持多线程并发调用。</para>
|
||||
/// </summary>
|
||||
/// <returns>初始化成功返回 true;若 SDK 核心组件(HCNetSDK.dll)加载失败则返回 false。</returns>
|
||||
public static bool Initialize()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// 引用计数为 0 时执行物理初始化(仅首次调用时触发)
|
||||
if (_referenceCount == 0)
|
||||
{
|
||||
// [物理初始化] 调用海康核心 DLL 接口,初始化 SDK 基础环境
|
||||
if (!HikNativeMethods.NET_DVR_Init()) return false;
|
||||
|
||||
// --- 工业级可靠性设置(注释保留,按需启用)---
|
||||
//// 1. 登录超时设置 (3000ms):
|
||||
//// 在高并发场景下,快速失败比无限重试更有利于系统调度。
|
||||
//HikNativeMethods.NET_DVR_SetConnectTime(3000, 1);
|
||||
|
||||
//// 2. [Fix Bug C: 双重重连冲突]
|
||||
//// 设计思路:禁用海康 SDK 内部的自动重连机制(bEnableRecon = false)。
|
||||
//// 理由:SDK 内部重连是非透明的,无法与我们的上层协调器 (Coordinator) 状态机完美对齐。
|
||||
//// 统一由外层协调器负责“检测断线 -> 销毁旧句柄 -> 重新登录”,确保状态的一致性。
|
||||
//HikNativeMethods.NET_DVR_SetReconnect(10000, false);
|
||||
}
|
||||
|
||||
// 引用计数递增,记录当前活跃的 SDK 使用者数量
|
||||
_referenceCount++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局卸载海康 SDK 环境。
|
||||
/// <para>当所有相机实例都停止并释放后(引用计数归 0),会真正释放非托管资源。</para>
|
||||
/// </summary>
|
||||
public static void Uninitialize()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// [Fix Bug S: 引用计数溢出保护]
|
||||
// 确保不会因为意外的多次调用导致计数器变为负数,避免逻辑异常
|
||||
if (_referenceCount > 0)
|
||||
{
|
||||
_referenceCount--;
|
||||
|
||||
// 引用计数归 0 时执行物理卸载,关闭 SDK 所有隐形线程与资源
|
||||
if (_referenceCount == 0)
|
||||
{
|
||||
// [物理卸载] 释放 SDK 占用的非托管资源(如网络连接、内存缓冲区)
|
||||
HikNativeMethods.NET_DVR_Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 播放库预热 (PlayCtrl Warm-up) ---
|
||||
|
||||
/// <summary>
|
||||
/// [核心策略] 强制冷启动诱发
|
||||
/// 职责:在系统真正取流前,强行触发一次 PlayCtrl.dll 的硬件探测(声卡、显卡、DirectDraw)
|
||||
/// 目的:规避首次取流时的 12-18 秒延迟,提前完成硬件初始化
|
||||
/// </summary>
|
||||
public static void ForceWarmUp()
|
||||
{
|
||||
// 已预热过则直接返回,避免重复执行
|
||||
if (_isWarmedUp) return;
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 正在进行播放库硬件探测预热,请稍候...");
|
||||
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
int tempPort = -1;
|
||||
|
||||
// 第一次调用 PlayM4_GetPort:触发 PlayCtrl.dll 底层硬件初始化(耗时主要集中在这里)
|
||||
if (HikPlayMethods.PlayM4_GetPort(ref tempPort))
|
||||
{
|
||||
// 必须释放临时端口,避免端口资源泄漏(PlayCtrl.dll 端口数量有限)
|
||||
HikPlayMethods.PlayM4_FreePort(tempPort);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
_isWarmedUp = true;
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 预热完成!耗时: {sw.ElapsedMilliseconds}ms。后续调用将恢复正常。");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
386
SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
Normal file
386
SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [海康驱动] 工业级视频源实现 V3.3.1 (极高并发修正版)
|
||||
/// 核心职责:负责海康威视设备 (SDK) 的物理连接、取流、解码与资源管理。
|
||||
/// 核心修复记录:
|
||||
/// 1. [Bug X] 异步竞争:引入 Epoch 世代验证,防止超时取消后的幽灵任务覆盖新连接。
|
||||
/// 2. [Bug Y] 内存踩踏:解码回调加锁,防止多核环境下共享 Mat 被并发读写引发 AV 异常。
|
||||
/// 3. [Bug α] 端口抢占:PlayM4_GetPort 全局加锁,防止高并发启动时的播放端口串位。
|
||||
/// 4. [Bug H/W/T/E] 继承之前的路由分发、幽灵句柄、零 GC、异步启动等修复。
|
||||
/// </summary>
|
||||
public class HikVideoSource : BaseVideoSource
|
||||
{
|
||||
#region --- 静态资源 (Global Resources) ---
|
||||
|
||||
// 静态路由表 (Fix Bug H: 友军误伤)
|
||||
// 作用:将海康 SDK 的全局回调(仅带 UserID)精准路由到具体的 HikVideoSource 实例
|
||||
private static readonly ConcurrentDictionary<int, HikVideoSource> _instances = new();
|
||||
|
||||
// 全局异常回调委托(防止 GC 回收)
|
||||
private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException;
|
||||
|
||||
// [Fix Bug α: 端口抢占]
|
||||
// 背景:海康播放库 PlayCtrl.dll 的 PlayM4_GetPort 函数内部使用了非线程安全的全局计数器。
|
||||
// 作用:使用全局静态锁强制串行化端口申请操作,防止高并发启动时分配到相同的 Port。
|
||||
private static readonly object _globalPortLock = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 实例成员 (Instance Members) ---
|
||||
|
||||
private int _userId = -1; // SDK 登录句柄(-1 表示未登录)
|
||||
private int _realPlayHandle = -1; // 预览句柄 (网络层,-1 表示未开启预览)
|
||||
private int _playPort = -1; // 播放端口 (解码层,-1 表示未分配端口)
|
||||
|
||||
private readonly object _initLock = new(); // 初始化/清理互斥锁:保护启动/停止流程的原子性
|
||||
private readonly object _bufferLock = new(); // 解码缓冲区锁 (Fix Bug Y: 防止多线程并发读写内存)
|
||||
|
||||
// [Fix Bug X: 异步状态竞争]
|
||||
// 作用:连接世代计数器,每次 StartAsync 调用时自增
|
||||
// 原理:异步任务执行过程中验证是否为最新请求,避免幽灵任务覆盖状态
|
||||
private volatile int _connectionEpoch = 0;
|
||||
|
||||
// 回调委托引用:必须持有以防止 P/Invoke 过程中被 GC 回收,导致回调崩溃
|
||||
private HikNativeMethods.REALDATACALLBACK? _realDataCallBack;
|
||||
private HikPlayMethods.DECCBFUN? _decCallBack;
|
||||
|
||||
// 内存复用对象 (Fix Bug T):复用非托管内存块,减少 LOH (大对象堆) 分配压力
|
||||
private Mat? _sharedYuvMat;
|
||||
private Mat? _sharedBgrMat;
|
||||
|
||||
// 帧对象池:实现零 GC 分配,避免频繁创建/销毁 Mat 导致的性能抖动
|
||||
private FramePool? _framePool;
|
||||
private bool _isPoolReady = false; // 帧池初始化状态标记
|
||||
|
||||
// 帧需求控制器:管理不同订阅者(UI/AI)的帧率需求,实现按需分发
|
||||
public FrameController Controller { get; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 构造函数 (Constructor) ---
|
||||
|
||||
/// <summary>
|
||||
/// 初始化海康视频源实例
|
||||
/// </summary>
|
||||
/// <param name="config">视频源基础配置(含设备IP、账号、码流类型等)</param>
|
||||
public HikVideoSource(VideoSourceConfig config) : base(config) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 核心生命周期 (Core Lifecycle) ---
|
||||
|
||||
/// <summary>
|
||||
/// [异步启动核心] (含 Bug E/X 修复)
|
||||
/// 流程:SDK环境初始化 → 注册全局回调 → 物理登录设备 → 路由注册 → 开启网络预览
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌:用于终止超时或中断的启动流程</param>
|
||||
protected override async Task OnStartAsync(CancellationToken token)
|
||||
{
|
||||
// [Fix Bug X] 记录当前启动世代,标记一次新的启动请求
|
||||
int currentEpoch = Interlocked.Increment(ref _connectionEpoch);
|
||||
|
||||
// [Fix Bug E] 切换到后台线程执行:避免阻塞UI/上下文线程(登录为同步阻塞操作)
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// [Fix Bug X] 世代验证:若已存在新的启动请求,直接放弃当前任务
|
||||
if (currentEpoch != _connectionEpoch) return;
|
||||
|
||||
// 初始化海康 SDK 环境(引用计数管理,确保资源不重复加载)
|
||||
if (!HikSdkManager.Initialize())
|
||||
throw new CameraException(CameraErrorCode.SdkNotInitialized, "SDK初始化失败", DeviceBrand.HikVision);
|
||||
|
||||
try
|
||||
{
|
||||
// 注册全局异常回调:捕获断线、重连等SDK层面异常
|
||||
HikNativeMethods.NET_DVR_SetExceptionCallBack_V30(0, IntPtr.Zero, _globalExceptionCallback, IntPtr.Zero);
|
||||
|
||||
// 执行设备物理登录(阻塞调用,网络异常时可能耗时数秒)
|
||||
var devInfo = new HikNativeMethods.NET_DEVICEINFO_V30();
|
||||
int newUserId = HikNativeMethods.NET_DVR_Login_V30(
|
||||
_config.IpAddress, _config.Port, _config.Username, _config.Password, ref devInfo);
|
||||
|
||||
// [Fix Bug X] 登录后再次验证世代:避免超时后产生的幽灵句柄
|
||||
if (currentEpoch != _connectionEpoch)
|
||||
{
|
||||
if (newUserId >= 0) HikNativeMethods.NET_DVR_Logout(newUserId); // 释放僵尸句柄
|
||||
throw new OperationCanceledException("启动任务已过期(被新的请求抢占)");
|
||||
}
|
||||
|
||||
_userId = newUserId;
|
||||
if (_userId < 0)
|
||||
{
|
||||
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
||||
throw new CameraException(HikErrorMapper.Map(err), $"登录失败: {err}", DeviceBrand.HikVision, (int)err);
|
||||
}
|
||||
|
||||
// [Bug H] 路由注册:将 UserID 与当前实例绑定,支持全局回调路由
|
||||
_instances.TryAdd(_userId, this);
|
||||
|
||||
// 开启网络预览(取流):失败则抛出异常,触发资源清理
|
||||
if (!StartRealPlay())
|
||||
{
|
||||
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
||||
throw new CameraException(HikErrorMapper.Map(err), $"预览失败: {err}", DeviceBrand.HikVision, (int)err);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// [Fix Bug W] 异常清理:启动失败时执行完整资源释放,防止句柄泄漏
|
||||
CleanupSync();
|
||||
throw;
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步停止设备:终止取流、解码,释放所有资源
|
||||
/// </summary>
|
||||
protected override async Task OnStopAsync()
|
||||
{
|
||||
// [Fix Bug X] 停止时递增世代:立即使所有正在进行的启动任务失效
|
||||
Interlocked.Increment(ref _connectionEpoch);
|
||||
|
||||
// 在后台线程执行同步清理逻辑,避免阻塞调用线程
|
||||
await Task.Run(() => CleanupSync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [同步清理核心] (含 Bug Y 锁保护)
|
||||
/// 职责:按“停止取流→释放解码资源→释放内存→注销登录”顺序销毁,防止非托管崩溃
|
||||
/// </summary>
|
||||
private void CleanupSync()
|
||||
{
|
||||
lock (_initLock)
|
||||
{
|
||||
// 1. 停止网络取流:释放预览句柄
|
||||
if (_realPlayHandle >= 0)
|
||||
{
|
||||
HikNativeMethods.NET_DVR_StopRealPlay(_realPlayHandle);
|
||||
_realPlayHandle = -1;
|
||||
}
|
||||
|
||||
// 2. 停止解码并释放播放端口:避免端口资源泄漏
|
||||
if (_playPort >= 0)
|
||||
{
|
||||
HikPlayMethods.PlayM4_Stop(_playPort);
|
||||
HikPlayMethods.PlayM4_CloseStream(_playPort);
|
||||
HikPlayMethods.PlayM4_FreePort(_playPort);
|
||||
_playPort = -1;
|
||||
}
|
||||
|
||||
// [Fix Bug Y] 内存释放保护:确保解码回调未在使用内存
|
||||
lock (_bufferLock)
|
||||
{
|
||||
_sharedYuvMat?.Dispose(); _sharedYuvMat = null;
|
||||
_sharedBgrMat?.Dispose(); _sharedBgrMat = null;
|
||||
}
|
||||
|
||||
// 3. 注销登录:先移除路由映射,再释放登录句柄
|
||||
if (_userId >= 0)
|
||||
{
|
||||
_instances.TryRemove(_userId, out _);
|
||||
HikNativeMethods.NET_DVR_Logout(_userId);
|
||||
_userId = -1;
|
||||
}
|
||||
|
||||
// 4. 释放帧对象池:清理复用内存
|
||||
_framePool?.Dispose();
|
||||
_framePool = null;
|
||||
_isPoolReady = false;
|
||||
}
|
||||
|
||||
// 5. 减少SDK全局引用计数:确保最后一个实例销毁时卸载SDK
|
||||
HikSdkManager.Uninitialize();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 网络取流 (Network Streaming) ---
|
||||
|
||||
/// <summary>
|
||||
/// 开启网络取流:配置预览参数,绑定流数据回调
|
||||
/// </summary>
|
||||
/// <returns>取流开启成功返回 true,失败返回 false</returns>
|
||||
private bool StartRealPlay()
|
||||
{
|
||||
var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO
|
||||
{
|
||||
hPlayWnd = IntPtr.Zero, // 句柄为空:SDK不直接渲染,通过回调获取原始流数据
|
||||
lChannel = _config.ChannelIndex, // 设备通道号(从配置读取)
|
||||
dwStreamType = (uint)_config.StreamType, // 码流类型(主码流/子码流,从配置读取)
|
||||
bBlocked = false // 非阻塞取流:避免长时间阻塞线程
|
||||
};
|
||||
|
||||
// 绑定网络流回调:接收SDK推送的原始流数据
|
||||
_realDataCallBack = new HikNativeMethods.REALDATACALLBACK(SafeOnRealDataReceived);
|
||||
_realPlayHandle = HikNativeMethods.NET_DVR_RealPlay_V40(_userId, ref previewInfo, _realDataCallBack, IntPtr.Zero);
|
||||
|
||||
return _realPlayHandle >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络流数据回调 (RealDataCallBack)
|
||||
/// 职责:接收 SDK 原始流数据,系统头用于初始化播放库,流数据用于解码
|
||||
/// </summary>
|
||||
private void SafeOnRealDataReceived(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 预览句柄无效时直接返回,避免无效处理
|
||||
if (_realPlayHandle == -1) return;
|
||||
|
||||
// 处理系统头:初始化播放库(仅首次接收系统头时执行)
|
||||
if (dwDataType == HikNativeMethods.NET_DVR_SYSHEAD && _playPort == -1)
|
||||
{
|
||||
lock (_initLock)
|
||||
{
|
||||
// 双重校验:防止多线程下重复初始化
|
||||
if (_realPlayHandle == -1 || _playPort != -1) return;
|
||||
|
||||
// [Fix Bug α: 端口抢占] 全局锁保护端口申请,避免并发冲突
|
||||
DateTime timeStart = DateTime.Now;
|
||||
bool getPortSuccess;
|
||||
lock (_globalPortLock)
|
||||
{
|
||||
getPortSuccess = HikPlayMethods.PlayM4_GetPort(ref _playPort);
|
||||
}
|
||||
var useTime = Math.Round((DateTime.Now - timeStart).TotalSeconds, 1);
|
||||
|
||||
if (!getPortSuccess) return;
|
||||
|
||||
// 关键配置:设置播放缓冲区为最小值1,减少延时(禁止播放库积压数据)
|
||||
HikPlayMethods.PlayM4_SetDisplayBuf(_playPort, 1);
|
||||
|
||||
// 初始化播放库:设置流模式→打开流→绑定解码回调→开始解码
|
||||
HikPlayMethods.PlayM4_SetStreamOpenMode(_playPort, 0); // 0=实时流模式
|
||||
if (!HikPlayMethods.PlayM4_OpenStream(_playPort, pBuffer, dwBufSize, 2 * 1024 * 1024))
|
||||
{
|
||||
HikPlayMethods.PlayM4_FreePort(_playPort);
|
||||
_playPort = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
_decCallBack = new HikPlayMethods.DECCBFUN(SafeOnDecodingCallBack);
|
||||
HikPlayMethods.PlayM4_SetDecCallBackEx(_playPort, _decCallBack, IntPtr.Zero, 0);
|
||||
HikPlayMethods.PlayM4_Play(_playPort, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
// 处理流数据:将原始流数据传入播放库解码
|
||||
else if (dwDataType == HikNativeMethods.NET_DVR_STREAMDATA && _playPort != -1)
|
||||
{
|
||||
HikPlayMethods.PlayM4_InputData(_playPort, pBuffer, dwBufSize);
|
||||
}
|
||||
}
|
||||
catch { /* 吞没回调异常:防止回调崩溃导致整个SDK进程退出 */ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 解码与帧分发 (Decoding & Frame Distribution) ---
|
||||
|
||||
/// <summary>
|
||||
/// 解码回调 (DecCallBack)
|
||||
/// 职责:接收解码后的 YUV 数据,转码为 BGR 格式,通过帧池复用内存并分发
|
||||
/// </summary>
|
||||
private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref HikPlayMethods.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2)
|
||||
{
|
||||
// 汇报心跳:更新帧接收时间,防止哨兵判定设备僵死
|
||||
MarkFrameReceived();
|
||||
|
||||
// 1. 帧分发决策:根据订阅者需求判断是否需要保留当前帧(耗时<0.01ms)
|
||||
var decision = Controller.MakeDecision(Environment.TickCount64);
|
||||
if (!decision.IsCaptured) return;
|
||||
|
||||
int width = pFrameInfo.nWidth;
|
||||
int height = pFrameInfo.nHeight;
|
||||
|
||||
// 2. 初始化帧池:首次解码时创建,按实际分辨率分配内存
|
||||
if (!_isPoolReady)
|
||||
{
|
||||
lock (_initLock)
|
||||
{
|
||||
if (!_isPoolReady)
|
||||
{
|
||||
_framePool?.Dispose();
|
||||
// 帧池配置:CV_8UC3=BGR格式,初始3帧,最大5帧(平衡内存与性能)
|
||||
_framePool = new FramePool(width, height, MatType.CV_8UC3, initialSize: 3, maxSize: 5);
|
||||
_isPoolReady = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_framePool == null) return;
|
||||
|
||||
// 3. 从帧池获取内存:零GC分配,池满时返回null(直接丢帧,避免积压)
|
||||
SmartFrame smartFrame = _framePool.Get();
|
||||
try
|
||||
{
|
||||
if (smartFrame == null) return; // 帧池满,丢弃当前帧
|
||||
|
||||
try
|
||||
{
|
||||
// 4. YUV转BGR:直接写入帧池内存,无中间对象分配
|
||||
using (var rawYuvWrapper = Mat.FromPixelData(height + height / 2, width, MatType.CV_8UC1, pBuf))
|
||||
{
|
||||
Cv2.CvtColor(rawYuvWrapper, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_YV12);
|
||||
}
|
||||
|
||||
// 5. 对外分发帧数据:通过基类事件通知订阅者(零拷贝)
|
||||
RaiseFrameReceived(smartFrame);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 异常时释放帧:避免内存泄漏
|
||||
smartFrame.Dispose();
|
||||
Debug.WriteLine($"[DecodingError] {ex.Message}");
|
||||
}
|
||||
|
||||
// 6. 提交到全局处理中心:后续由管道处理二次加工与分发
|
||||
GlobalProcessingCenter.Submit(this.Id, smartFrame, decision);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 释放驱动层引用:驱动职责结束,引用计数-1(由消费者/管道管理后续生命周期)
|
||||
smartFrame.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 异常处理 (Exception Handling) ---
|
||||
|
||||
/// <summary>
|
||||
/// 全局异常回调处理
|
||||
/// 职责:将 SDK 全局异常(仅带 UserID)路由到对应的 HikVideoSource 实例
|
||||
/// </summary>
|
||||
private static void StaticOnSdkException(uint dwType, int lUserID, int lHandle, IntPtr pUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通过 UserID 查找实例,触发实例内异常处理逻辑
|
||||
if (_instances.TryGetValue(lUserID, out var instance))
|
||||
{
|
||||
instance.ReportError(new CameraException(
|
||||
CameraErrorCode.NetworkUnreachable,
|
||||
$"SDK全局报警异常: 0x{dwType:X}",
|
||||
DeviceBrand.HikVision));
|
||||
}
|
||||
}
|
||||
catch { /* 吞没异常:避免全局回调崩溃 */ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 元数据获取 (Metadata Fetching) ---
|
||||
|
||||
/// <summary>
|
||||
/// 占位实现:暂未实现设备元数据获取逻辑
|
||||
/// 注:实际场景需补充,用于获取设备型号、通道能力等信息
|
||||
/// </summary>
|
||||
protected override Task<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user