海康摄像头取流示例初始签入
This commit is contained in:
62
SHH.CameraSdk/Abstractions/Enums/DeviceBrand.cs
Normal file
62
SHH.CameraSdk/Abstractions/Enums/DeviceBrand.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 视频源物理/逻辑品牌类型
|
||||
/// 职责:用于工厂模式匹配具体的 IVideoSource 实现类,并定义基础通信协议栈
|
||||
/// </summary>
|
||||
public enum DeviceBrand
|
||||
{
|
||||
/// <summary>
|
||||
/// 未知
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 海康威视 (HikVision)
|
||||
/// 技术路径:基于海康私有 SDK (HCNetSDK.dll / PlayCtrl.dll)。
|
||||
/// 特性:支持全功能控制(PTZ、对讲、配置、报警回传)。
|
||||
/// </summary>
|
||||
HikVision,
|
||||
|
||||
/// <summary>
|
||||
/// 大华 (Dahua)
|
||||
/// 技术路径:基于大华私有 SDK (dhnetsdk.dll / dhplay.dll)。
|
||||
/// 特性:支持全功能控制,与海康私有协议不兼容。
|
||||
/// </summary>
|
||||
Dahua,
|
||||
|
||||
/// <summary>
|
||||
/// USB 摄像头 / 虚拟摄像头
|
||||
/// 技术路径:基于 DirectShow 或 Windows Media Foundation。
|
||||
/// 特性:通常通过 OpenCV (VideoCapture) 或 DirectShowLib 直接读取本地硬件引用。
|
||||
/// </summary>
|
||||
Usb,
|
||||
|
||||
/// <summary>
|
||||
/// 标准 RTSP 流媒体
|
||||
/// 技术路径:基于标准 RTSP/RTP 协议 (RFC 2326)。
|
||||
/// 特性:跨品牌兼容,通常使用 FFmpeg 或 GStreamer 库取流,仅支持音视频,不支持云台控制。
|
||||
/// </summary>
|
||||
RtspGeneral,
|
||||
|
||||
/// <summary>
|
||||
/// 三恒自研 WebSocket 流
|
||||
/// 技术路径:基于 WebSocket 传输的自定义二进制或 Base64 帧。
|
||||
/// 特性:专用于 Web 或云端推送场景的私有流媒体格式。
|
||||
/// </summary>
|
||||
WebSocketShine,
|
||||
|
||||
/// <summary>
|
||||
/// 本地视频文件
|
||||
/// 技术路径:基于文件 IO 的离线解码。
|
||||
/// 特性:常用于算法演示、回放模拟,支持 Mp4, Avi, Mkv 等容器格式。
|
||||
/// </summary>
|
||||
File,
|
||||
|
||||
/// <summary>
|
||||
/// 未知/通用标准 (ONVIF)
|
||||
/// 技术路径:基于标准 ONVIF WebService。
|
||||
/// 特性:用于接入非主流厂商但符合 ONVIF 标准的设备,支持基础 PTZ。
|
||||
/// </summary>
|
||||
OnvifGeneral
|
||||
}
|
||||
16
SHH.CameraSdk/Abstractions/Enums/TransportProtocol.cs
Normal file
16
SHH.CameraSdk/Abstractions/Enums/TransportProtocol.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 网络传输协议类型
|
||||
/// </summary>
|
||||
public enum TransportProtocol
|
||||
{
|
||||
/// <summary> 可靠传输 (默认) </summary>
|
||||
Tcp = 0,
|
||||
|
||||
/// <summary> 快速传输 (可能丢包/花屏) </summary>
|
||||
Udp = 1,
|
||||
|
||||
/// <summary> 组播 (节省带宽) </summary>
|
||||
Multicast = 2
|
||||
}
|
||||
57
SHH.CameraSdk/Abstractions/Enums/VideoSourceStatus.cs
Normal file
57
SHH.CameraSdk/Abstractions/Enums/VideoSourceStatus.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 视频源逻辑状态枚举
|
||||
/// 描述了从配置加载到视频流稳定输出的完整生命周期
|
||||
/// </summary>
|
||||
public enum VideoSourceStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 已断开/初始状态。
|
||||
/// 此时资源已释放,尚未执行 Login 或 Start 操作。
|
||||
/// </summary>
|
||||
Disconnected,
|
||||
|
||||
/// <summary>
|
||||
/// 正在尝试建立网络连接。
|
||||
/// 此时正在进行 Socket 握手或探测设备 IP 是否可达。
|
||||
/// </summary>
|
||||
Connecting,
|
||||
|
||||
/// <summary>
|
||||
/// 正在进行身份验证。
|
||||
/// 连接已建立,正在提交 UserName/Password 调用 SDK 的 Login 接口。
|
||||
/// </summary>
|
||||
Authorizing,
|
||||
|
||||
/// <summary>
|
||||
/// 已登录/待机。
|
||||
/// 登录成功并获取到了设备元数据(Metadata),但尚未启动预览(RealPlay)。
|
||||
/// 适用于“仅管理,不看画面”的场景。
|
||||
/// </summary>
|
||||
Connected,
|
||||
|
||||
/// <summary>
|
||||
/// 正常取流播放中
|
||||
/// </summary>
|
||||
Playing,
|
||||
|
||||
/// <summary>
|
||||
/// 正在取流/正常运行中。
|
||||
/// 预览句柄已开启,取流回调函数正在持续接收数据帧并进行解码。
|
||||
/// </summary>
|
||||
Streaming,
|
||||
|
||||
/// <summary>
|
||||
/// 自动重连中。
|
||||
/// 检测到网络抖动或心跳丢失,SDK 正在尝试内部恢复,此时视频流可能处于停滞状态。
|
||||
/// </summary>
|
||||
Reconnecting,
|
||||
|
||||
/// <summary>
|
||||
/// 故障/异常状态。
|
||||
/// 发生了不可恢复的错误(如密码错误、最大连接数限制、设备强制离线)。
|
||||
/// 进入此状态通常需要人工干预或调用 Stop 后重新 Start。
|
||||
/// </summary>
|
||||
Faulted
|
||||
}
|
||||
226
SHH.CameraSdk/Abstractions/Errors/CameraErrorCode.cs
Normal file
226
SHH.CameraSdk/Abstractions/Errors/CameraErrorCode.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 工业级相机归一化错误码 (修正全量版)
|
||||
/// 职责:跨厂家建立统一故障语义,支撑 HikErrorMapper 等驱动层的精准映射。
|
||||
/// </summary>
|
||||
public enum CameraErrorCode
|
||||
{
|
||||
[Description("操作成功")]
|
||||
Success = 0,
|
||||
|
||||
#region --- 1000-1499 运行环境与 SDK 基础故障 ---
|
||||
|
||||
[Description("SDK 未初始化")]
|
||||
SdkNotInitialized = 1000,
|
||||
|
||||
[Description("SDK 资源分配错误或本地内存不足")]
|
||||
LocalResourceError = 1001,
|
||||
|
||||
[Description("加载插件/组件失败:缺少 DLL 或依赖库")]
|
||||
ComponentLoadFailed = 1002,
|
||||
|
||||
[Description("组件版本不匹配")]
|
||||
ComponentVersionMismatch = 1003,
|
||||
|
||||
[Description("加载加密库失败(Ope nSSL/LibEay32)")]
|
||||
EncryptionLibError = 1004, // 已补齐 (海康 156)
|
||||
|
||||
[Description("函数调用顺序错误")]
|
||||
FunctionOrderError = 1005,
|
||||
|
||||
[Description("操作系统不支持该功能")]
|
||||
OsNotSupported = 1006,
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 1500-1999 网络通信与协议故障 ---
|
||||
|
||||
[Description("连接设备失败:设备离线")]
|
||||
NetworkUnreachable = 1500,
|
||||
|
||||
[Description("交互超时:网络拥塞或设备响应慢")]
|
||||
Timeout = 1501,
|
||||
|
||||
[Description("数据发送失败")]
|
||||
NetworkSendError = 1502,
|
||||
|
||||
[Description("数据接收失败")]
|
||||
NetworkRecvError = 1503,
|
||||
|
||||
[Description("网络套接字(Socket)异常")]
|
||||
SocketError = 1504,
|
||||
|
||||
[Description("IP 地址冲突")]
|
||||
IpConflict = 1505,
|
||||
|
||||
[Description("端口池耗尽或端口复用失败")]
|
||||
PortPoolExhausted = 1506,
|
||||
|
||||
[Description("连接已失效或未建立")]
|
||||
InvalidLink = 1507, // 已补齐 (海康 188)
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2000-2499 身份认证与权限管理 ---
|
||||
|
||||
[Description("用户名或密码错误")]
|
||||
InvalidCredentials = 2000,
|
||||
|
||||
[Description("用户权限不足")]
|
||||
AccessDenied = 2001,
|
||||
|
||||
[Description("用户不存在")]
|
||||
UserNotExist = 2002,
|
||||
|
||||
[Description("账号已被锁定(多次尝试失败)")]
|
||||
AccountLocked = 2003,
|
||||
|
||||
[Description("登录人数已达上限")]
|
||||
MaxUserExceeded = 2004,
|
||||
|
||||
[Description("会话已过期或已被强行踢出")]
|
||||
SessionExpired = 2005,
|
||||
|
||||
[Description("用户正在使用中(如正在对讲/升级)")]
|
||||
UserInUse = 2006, // 已补齐 (海康 74)
|
||||
|
||||
[Description("登录版本过低(不支持该协议)")]
|
||||
LoginVersionLow = 2007, // 已补齐 (海康 155)
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2500-2999 设备资源与负载限制 ---
|
||||
|
||||
[Description("设备连接数已达上限")]
|
||||
MaxConnectionsReached = 2500,
|
||||
|
||||
[Description("设备资源不足或内部忙")]
|
||||
DeviceResourceBusy = 2501,
|
||||
|
||||
[Description("通道接入数达到上限")]
|
||||
MaxQuantityExceeded = 2502,
|
||||
|
||||
[Description("主/子码流路数超限")]
|
||||
MaxStreamExceeded = 2503,
|
||||
|
||||
[Description("设备缓冲区不足/溢出")]
|
||||
DeviceBufferOverflow = 2504,
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3000-3499 视频预览、回放与解码 ---
|
||||
|
||||
[Description("预览失败或通道未编码")]
|
||||
PreviewFailed = 3000,
|
||||
|
||||
[Description("码流封装格式不支持")]
|
||||
StreamTypeNotSupport = 3001,
|
||||
|
||||
[Description("码流数据中断(丢包/心跳丢失)")]
|
||||
StreamInterrupted = 3002,
|
||||
|
||||
[Description("码流已加密(需二次认证)")]
|
||||
StreamEncrypted = 3003,
|
||||
|
||||
[Description("外接 IP 通道离线")]
|
||||
IpChannelOffline = 3004,
|
||||
|
||||
[Description("设备通道异常")]
|
||||
ChannelException = 3005, // 已补齐 (海康 18)
|
||||
|
||||
[Description("播放库(Player SDK)调用失败")]
|
||||
PlayerSdkFailed = 3006, // 已补齐 (海康 51)
|
||||
|
||||
[Description("音频设备忙(声卡被独占)")]
|
||||
AudioDeviceBusy = 3007, // 已补齐 (海康 69)
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3500-3999 存储管理故障 ---
|
||||
|
||||
[Description("存储设备通用错误")]
|
||||
StorageError = 3500,
|
||||
|
||||
[Description("设备无硬盘")]
|
||||
NoDisk = 3501,
|
||||
|
||||
[Description("硬盘已满")]
|
||||
DiskFull = 3502,
|
||||
|
||||
[Description("硬盘状态异常(格式化中或读写错)")]
|
||||
DiskStatusError = 3503,
|
||||
|
||||
[Description("尝试格式化只读硬盘")]
|
||||
DiskReadOnly = 3504,
|
||||
|
||||
[Description("存储池/NAS 目录无效")]
|
||||
StoragePoolError = 3505,
|
||||
|
||||
[Description("写入存储(Flash/文件)失败")]
|
||||
WriteStorageFailed = 3506, // 已补齐 (海康 48, 77)
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 4000-4499 硬件、参数与系统故障 ---
|
||||
|
||||
[Description("硬件内部故障")]
|
||||
HardwareFault = 4000,
|
||||
|
||||
[Description("通道号错误或不存在")]
|
||||
InvalidChannel = 4001,
|
||||
|
||||
[Description("参数错误(空指针或无效值)")]
|
||||
InvalidParameter = 4002,
|
||||
|
||||
[Description("视频信号丢失(黑屏/丢信号)")]
|
||||
VideoSignalLoss = 4003,
|
||||
|
||||
[Description("设备正在重启中")]
|
||||
DeviceRebooting = 4004,
|
||||
|
||||
[Description("需重启生效")]
|
||||
RebootRequired = 4005,
|
||||
|
||||
[Description("时间输入错误")]
|
||||
InvalidTimeInput = 4006, // 已补齐 (海康 32)
|
||||
|
||||
[Description("设备型号或版本不匹配")]
|
||||
DeviceMismatch = 4007, // 已补齐 (海康 80)
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 4500-4999 操作限制与通用状态 ---
|
||||
|
||||
[Description("设备不支持该功能")]
|
||||
NotSupported = 4500, // 已补齐 (海康 23)
|
||||
|
||||
[Description("修改或设置失败")]
|
||||
ModifyFailed = 4501,
|
||||
|
||||
[Description("不支持无阻塞抓图")]
|
||||
CaptureNotSupport = 4502,
|
||||
|
||||
[Description("设备忙")]
|
||||
DeviceBusy = 4503,
|
||||
|
||||
[Description("上次操作未完成")]
|
||||
OperationNotFinished = 4504,
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 9000-9999 系统级故障 ---
|
||||
|
||||
[Description("驱动未实现该功能")]
|
||||
NotImplemented = 9001,
|
||||
|
||||
[Description("程序异常")]
|
||||
ProgramException = 9998,
|
||||
|
||||
[Description("未知错误")]
|
||||
Unknown = 9999
|
||||
|
||||
#endregion
|
||||
}
|
||||
107
SHH.CameraSdk/Abstractions/Errors/CameraException.cs
Normal file
107
SHH.CameraSdk/Abstractions/Errors/CameraException.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 视频 SDK 统一异常类 (V3.3.1 修复版)
|
||||
/// 核心职责:
|
||||
/// <para>1. 封装标准化错误码、厂商原始错误码、设备品牌信息</para>
|
||||
/// <para>2. 记录异常上下文快照,辅助故障定位与复盘</para>
|
||||
/// 协作关系:
|
||||
/// <para>1. 与 <see cref="HikErrorMapper"/> 配合:实现厂商错误码→标准错误码的转换</para>
|
||||
/// <para>2. 与 <see cref="RecoveryPolicy"/> 配合:提供错误码输入,驱动故障自愈决策</para>
|
||||
/// </summary>
|
||||
public class CameraException : Exception
|
||||
{
|
||||
#region --- 核心异常属性 (Core Exception Properties) ---
|
||||
|
||||
/// <summary>
|
||||
/// 归一化后的标准错误码
|
||||
/// 业务用途:作为 RecoveryPolicy 的决策输入,屏蔽厂商差异
|
||||
/// </summary>
|
||||
public CameraErrorCode ErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 厂商原始错误码(如海康 NET_DVR_GetLastError、大华 SDK 原生错误码)
|
||||
/// 业务用途:厂商文档对照、深度问题排查
|
||||
/// </summary>
|
||||
public int RawErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 发生异常的设备品牌
|
||||
/// 业务用途:区分不同厂商的错误码规则,辅助错误映射
|
||||
/// </summary>
|
||||
public DeviceBrand Brand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常发生时的上下文快照(只读集合,防止外部篡改)
|
||||
/// 存储内容:设备IP、通道号、操作参数、SDK句柄、时间戳等案发现场信息
|
||||
/// 业务用途:故障复盘时还原现场,快速定位根因
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object> Context { get; init; } = new Dictionary<string, object>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 构造函数 (Constructors) ---
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 CameraException 实例
|
||||
/// </summary>
|
||||
/// <param name="errorCode">归一化标准错误码</param>
|
||||
/// <param name="message">异常描述信息</param>
|
||||
/// <param name="brand">设备品牌</param>
|
||||
/// <param name="rawErrorCode">厂商原始错误码(默认 0)</param>
|
||||
/// <param name="innerException">内部异常(默认 null)</param>
|
||||
public CameraException(
|
||||
CameraErrorCode errorCode,
|
||||
string message,
|
||||
DeviceBrand brand,
|
||||
int rawErrorCode = 0,
|
||||
Exception? innerException = null)
|
||||
: base(message, innerException)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
Brand = brand;
|
||||
RawErrorCode = rawErrorCode;
|
||||
// 初始化上下文字典为可写的 Dictionary,兼容 WithContext 方法
|
||||
Context = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 工具方法 (Utility Methods) ---
|
||||
|
||||
/// <summary>
|
||||
/// 链式添加上下文信息(Builder 模式)
|
||||
/// 业务用途:在抛出异常前,逐步追加案发现场信息
|
||||
/// </summary>
|
||||
/// <param name="key">上下文键(如 "DeviceIp", "ChannelIndex")</param>
|
||||
/// <param name="value">上下文值</param>
|
||||
/// <returns>当前异常实例(支持链式调用)</returns>
|
||||
public CameraException WithContext(string key, object value)
|
||||
{
|
||||
// 强制转换为可写的 Dictionary,保证上下文可追加
|
||||
if (Context is Dictionary<string, object> contextDict)
|
||||
{
|
||||
contextDict[key] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写 ToString 方法,输出标准化异常日志
|
||||
/// 格式:[CameraError] Brand: {品牌} | Code: {标准码}({原始码}) | Message: {描述} | Context: {上下文}
|
||||
/// </summary>
|
||||
/// <returns>格式化的异常字符串</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var contextStr = Context.Count > 0
|
||||
? $" | Context: {string.Join(", ", Context.Select(kvp => $"{kvp.Key}={kvp.Value}"))}"
|
||||
: string.Empty;
|
||||
|
||||
return $"[CameraError] Brand: {Brand} | Code: {ErrorCode}({RawErrorCode}) | Message: {Message}{contextStr}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
55
SHH.CameraSdk/Abstractions/Errors/RecoveryAction.cs
Normal file
55
SHH.CameraSdk/Abstractions/Errors/RecoveryAction.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 故障恢复决策建议枚举
|
||||
/// 核心职责:定义标准化的故障自愈动作指令,指导 <see cref="RecoveryPolicy"/> 与 <see cref="BaseVideoSource"/> 执行差异化恢复逻辑
|
||||
/// 设计原则:按“无动作→自动恢复→降级→致命停止→人工介入”的优先级划分,覆盖全场景故障处理
|
||||
/// </summary>
|
||||
public enum RecoveryAction
|
||||
{
|
||||
#region --- 0. 基础状态 ---
|
||||
|
||||
/// <summary>
|
||||
/// 正常状态,无需执行任何恢复动作
|
||||
/// 适用场景:错误码为 Success、设备运行正常
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 1. 自动恢复动作 ---
|
||||
|
||||
/// <summary>
|
||||
/// 自动指数退避重试
|
||||
/// 适用场景:网络抖动、超时、设备资源繁忙等**暂时性故障**
|
||||
/// 执行标准:采用 2^n * 1000ms 算法计算延迟,上限 2 分钟,避免频繁重试加剧系统负载
|
||||
/// </summary>
|
||||
RetryWithBackoff,
|
||||
|
||||
/// <summary>
|
||||
/// 降级运行
|
||||
/// 适用场景:主码流超限、高清分辨率不支持等**非致命功能降级场景**
|
||||
/// 执行标准:自动切换到备用方案(如主码流→子码流、4K→1080P),保证基础功能可用
|
||||
/// </summary>
|
||||
Degrade,
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 终止与人工动作 ---
|
||||
|
||||
/// <summary>
|
||||
/// 致命停止,禁止继续重试
|
||||
/// 适用场景:密码错误、账号锁定、IP 拉黑等**不可自愈的认证/权限类故障**
|
||||
/// 执行标准:立即停止自愈引擎,推送告警信息到运维平台,记录详细错误日志
|
||||
/// </summary>
|
||||
FatalStop,
|
||||
|
||||
/// <summary>
|
||||
/// 需要人工介入处理
|
||||
/// 适用场景:硬件故障、磁盘满、SDK 组件缺失等**软件无法修复的底层故障**
|
||||
/// 执行标准:触发告警通知,标记设备状态为 Faulted,等待运维人员排查
|
||||
/// </summary>
|
||||
ManualIntervention
|
||||
|
||||
#endregion
|
||||
}
|
||||
100
SHH.CameraSdk/Abstractions/Errors/RecoveryPolicy.cs
Normal file
100
SHH.CameraSdk/Abstractions/Errors/RecoveryPolicy.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [决策引擎] 故障自愈策略 (V3.3.1 修复版)
|
||||
/// 核心职责:根据设备错误码特征,智能裁决系统应采取的恢复动作,实现故障自动化处理
|
||||
/// 关键修复(Bug R):
|
||||
/// <para>1. 致命错误防护:对 InvalidCredentials/AccountLocked 等错误禁止重试,防止账号被锁、IP 拉黑</para>
|
||||
/// <para>2. 未知错误保守策略:对 Unknown 错误采用 ManualIntervention,避免未知风险扩散</para>
|
||||
/// 设计原则:最小化风险、最大化自愈率,区分可重试/不可重试/需人工干预的错误类型
|
||||
/// </summary>
|
||||
public static class RecoveryPolicy
|
||||
{
|
||||
#region --- 1. 核心决策逻辑:错误码→自愈动作映射 ---
|
||||
|
||||
/// <summary>
|
||||
/// 根据相机错误码判定对应的故障自愈动作
|
||||
/// </summary>
|
||||
/// <param name="code">设备上报的错误码</param>
|
||||
/// <returns>标准化的自愈动作指令</returns>
|
||||
public static RecoveryAction GetAction(CameraErrorCode code)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
// ========== 场景 A: 网络类故障 (可自愈) ==========
|
||||
// 策略:指数退避重试
|
||||
// 理由:网络波动、超时、闪断为暂时性故障,延迟重试大概率恢复
|
||||
CameraErrorCode.NetworkUnreachable or
|
||||
CameraErrorCode.NetworkSendError or
|
||||
CameraErrorCode.NetworkRecvError or
|
||||
CameraErrorCode.Timeout or
|
||||
CameraErrorCode.SocketError or
|
||||
CameraErrorCode.StreamInterrupted or
|
||||
CameraErrorCode.DeviceRebooting => RecoveryAction.RetryWithBackoff,
|
||||
|
||||
// ========== 场景 B: 资源繁忙类故障 (可自愈) ==========
|
||||
// 策略:指数退避重试
|
||||
// 理由:设备连接数满、缓冲区溢出,等待资源释放后可恢复
|
||||
CameraErrorCode.DeviceResourceBusy or
|
||||
CameraErrorCode.DeviceBufferOverflow or
|
||||
CameraErrorCode.DeviceBusy or
|
||||
CameraErrorCode.OperationNotFinished or
|
||||
CameraErrorCode.PortPoolExhausted or
|
||||
CameraErrorCode.MaxConnectionsReached or
|
||||
CameraErrorCode.MaxStreamExceeded => RecoveryAction.RetryWithBackoff,
|
||||
|
||||
// ========== 场景 C: 致命错误 (不可自愈,禁止重试) ==========
|
||||
// 策略:立即停止
|
||||
// 理由:密码错误、账号锁定、组件缺失等故障,重试无意义且会加剧风险(账号锁死、日志爆炸)
|
||||
CameraErrorCode.InvalidCredentials or
|
||||
CameraErrorCode.AccessDenied or
|
||||
CameraErrorCode.UserNotExist or
|
||||
CameraErrorCode.AccountLocked or
|
||||
CameraErrorCode.SessionExpired or
|
||||
CameraErrorCode.InvalidChannel or
|
||||
CameraErrorCode.IpConflict or
|
||||
CameraErrorCode.SdkNotInitialized or
|
||||
CameraErrorCode.ComponentLoadFailed or
|
||||
CameraErrorCode.EncryptionLibError => RecoveryAction.FatalStop,
|
||||
|
||||
// ========== 场景 D: 硬件故障 (需人工干预) ==========
|
||||
// 策略:人工介入
|
||||
// 理由:硬盘损坏、存储满等故障属于硬件层面,软件无法修复
|
||||
CameraErrorCode.HardwareFault or
|
||||
CameraErrorCode.StorageError or
|
||||
CameraErrorCode.DiskFull or
|
||||
CameraErrorCode.DiskReadOnly => RecoveryAction.ManualIntervention,
|
||||
|
||||
// ========== 场景 E: 正常状态 ==========
|
||||
CameraErrorCode.Success => RecoveryAction.None,
|
||||
|
||||
// ========== 场景 F: 未知错误 (关键修复 Bug R) ==========
|
||||
// 旧策略:盲目重试 → 新策略:人工干预
|
||||
// 理由:未知错误可能包含 IP 拉黑、协议不兼容等严重问题,重试会扩大风险
|
||||
_ => RecoveryAction.ManualIntervention
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 辅助算法:指数退避延迟计算 ---
|
||||
|
||||
/// <summary>
|
||||
/// 获取建议的指数退避延迟时间(毫秒)
|
||||
/// 算法公式:delay = min(2^n * 1000, 120000),n = 当前重试次数
|
||||
/// 限流规则:第一次 2s → 第二次 4s → ... → 第六次 64s → 上限 120s(2分钟)
|
||||
/// </summary>
|
||||
/// <param name="retryCount">当前重试次数(从 1 开始计数)</param>
|
||||
/// <returns>延迟毫秒数</returns>
|
||||
public static int GetRetryDelay(int retryCount)
|
||||
{
|
||||
// 限制重试次数最大为 7,防止指数爆炸导致数值溢出
|
||||
int exponent = Math.Min(retryCount, 7);
|
||||
// 计算指数退避秒数
|
||||
int delaySeconds = (int)Math.Pow(2, exponent);
|
||||
// 转换为毫秒并限制上限为 2 分钟(120000ms)
|
||||
return Math.Min(delaySeconds * 1000, 120000);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
91
SHH.CameraSdk/Abstractions/IVideoSource.cs
Normal file
91
SHH.CameraSdk/Abstractions/IVideoSource.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [核心契约] 工业级视频源接口 (V3.3.1 终极定稿)
|
||||
/// 核心职责:定义所有视频源设备的标准化生命周期、状态观测与数据分发能力
|
||||
/// 关键修复:
|
||||
/// <para>1. [Fix Bug δ] 新增 UpdateConfig 接口,支持运行时配置热更新</para>
|
||||
/// <para>2. 强化资源管理契约:继承 IDisposable/IAsyncDisposable,规范非托管资源释放</para>
|
||||
/// 适用场景:海康/大华/宇视等不同品牌相机的驱动适配、统一管理
|
||||
/// </summary>
|
||||
public interface IVideoSource : IDisposable, IAsyncDisposable
|
||||
{
|
||||
#region --- 1. 只读属性 (设备标识与状态观测) ---
|
||||
|
||||
/// <summary> 设备唯一业务标识(全局唯一,如数据库自增ID) </summary>
|
||||
long Id { get; }
|
||||
|
||||
/// <summary> 设备详细逻辑状态(如 Idle/Connecting/Playing/Faulted) </summary>
|
||||
VideoSourceStatus Status { get; }
|
||||
|
||||
/// <summary> 用户意图标识:是否需要保持设备运行状态 </summary>
|
||||
bool IsRunning { get; set; }
|
||||
|
||||
/// <summary> 设备物理在线状态(基于心跳/探测的实时感知结果) </summary>
|
||||
bool IsOnline { get; }
|
||||
|
||||
/// <summary> 设备能力元数据(只读,如分辨率、码流类型、支持的功能集) </summary>
|
||||
DeviceMetadata Metadata { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 事件契约 (数据分发与状态通知) ---
|
||||
|
||||
/// <summary>
|
||||
/// 视频帧接收事件(热路径,高频触发)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 1. 载荷类型:通常为 <see cref="OpenCvSharp.Mat"/> 或 <see cref="SmartFrame"/> 对象
|
||||
/// 2. 内存管理:订阅者必须负责载荷对象的 Dispose 操作,否则会导致内存泄漏
|
||||
/// 3. 性能约束:事件处理逻辑需控制在 10ms 内,避免阻塞取流线程
|
||||
/// </remarks>
|
||||
event Action<object>? FrameReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 设备状态变更通知事件(结构化状态同步)
|
||||
/// </summary>
|
||||
/// <remarks> 携带状态变更前后的详细信息,用于监控告警、日志记录 </remarks>
|
||||
event EventHandler<StatusChangedEventArgs> StatusChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3. 核心方法 (生命周期与配置管理) ---
|
||||
|
||||
/// <summary>
|
||||
/// 异步启动设备(完整流程:连接设备 → 登录鉴权 → 启动码流接收)
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">设备状态非法时抛出</exception>
|
||||
/// <exception cref="SdkCommunicationException">SDK 通信失败时抛出</exception>
|
||||
Task StartAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 异步停止设备(完整流程:停止码流 → 登出设备 → 释放连接资源)
|
||||
/// </summary>
|
||||
Task StopAsync();
|
||||
|
||||
/// <summary>
|
||||
/// [Fix Bug δ] 运行时更新设备配置
|
||||
/// </summary>
|
||||
/// <param name="newConfig">新的设备配置(如 IP、端口、用户名密码)</param>
|
||||
/// <remarks>
|
||||
/// 1. 生效机制:新配置不会立即生效,将在下次启动或自动重连时应用
|
||||
/// 2. 原子性保证:配置更新为原子操作,不会出现部分生效的情况
|
||||
/// 3. 适用场景:设备 IP 变更、密码修改等运维场景
|
||||
/// </remarks>
|
||||
void UpdateConfig(VideoSourceConfig newConfig);
|
||||
|
||||
/// <summary>
|
||||
/// 应用动态流配置补丁(无需重启,实时生效)
|
||||
/// </summary>
|
||||
/// <param name="options">动态流参数(如主码流/子码流切换、分辨率调整)</param>
|
||||
/// <remarks> 适用于运行时按需调整码流参数,降低带宽占用 </remarks>
|
||||
void ApplyOptions(DynamicStreamOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// 强制刷新设备元数据,并返回元数据变更差异
|
||||
/// </summary>
|
||||
/// <returns>元数据变更差异对象(如分辨率变化、功能集变化)</returns>
|
||||
Task<MetadataDiff> RefreshMetadataAsync();
|
||||
|
||||
#endregion
|
||||
}
|
||||
107
SHH.CameraSdk/Abstractions/Models/ChannelMetadata.cs
Normal file
107
SHH.CameraSdk/Abstractions/Models/ChannelMetadata.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 通道级能力描述(镜头身份证)
|
||||
/// 核心职责:描述单个物理镜头或 NVR 通道的技术参数、功能支持特性与分辨率能力
|
||||
/// 协作场景:作为 <see cref="DeviceMetadata"/> 的子级数据,支撑设备能力自发现、配置合法性校验
|
||||
/// </summary>
|
||||
public class ChannelMetadata
|
||||
{
|
||||
#region --- 1. 通道基础标识 (Basic Identification) ---
|
||||
|
||||
/// <summary>
|
||||
/// 物理通道索引(从 1 开始计数,与 DVR/NVR 物理通道号一一对应)
|
||||
/// 业务用途:作为通道唯一标识,用于码流订阅、参数配置
|
||||
/// </summary>
|
||||
public int ChannelIndex { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道名称(用于 OSD 叠加显示、UI 界面展示)
|
||||
/// 示例:"北大门主入口" "地下车库A区"
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 功能支持特性 (Capability Support) ---
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持云台控制(PTZ:Pan/Tilt/Zoom 平移/俯仰/变焦)
|
||||
/// 业务影响:决定 UI 是否显示云台控制按钮,是否允许下发 PTZ 指令
|
||||
/// </summary>
|
||||
public bool SupportPtz { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持音频输入(是否接入拾音器)
|
||||
/// 业务影响:决定是否开启音频解码、音频流推送功能
|
||||
/// </summary>
|
||||
public bool SupportAudioIn { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持 AI 智能分析(人脸检测、车牌识别、行为分析等)
|
||||
/// 业务影响:决定是否加载 AI 算法插件,是否接收智能事件上报
|
||||
/// </summary>
|
||||
public bool SupportAiAnalysis { get; init; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3. 分辨率能力 (Resolution Capabilities) ---
|
||||
|
||||
/// <summary>
|
||||
/// 通道支持的分辨率列表(只读集合,防止外部篡改)
|
||||
/// 格式示例:["1920x1080", "1280x720", "3840x2160"]
|
||||
/// 业务用途:前端清晰度选择下拉列表、动态配置分辨率合法性校验
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<string> SupportedResolutions { get; init; } = new ReadOnlyCollection<string>(new List<string>());
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 4. 构造函数 (Constructors) ---
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造函数(用于序列化、初始状态初始化)
|
||||
/// </summary>
|
||||
public ChannelMetadata() { }
|
||||
|
||||
/// <summary>
|
||||
/// 简化构造函数(用于快速创建通道标识、克隆或比对场景)
|
||||
/// </summary>
|
||||
/// <param name="index">物理通道索引</param>
|
||||
/// <param name="name">通道名称</param>
|
||||
public ChannelMetadata(int index, string name)
|
||||
{
|
||||
ChannelIndex = index;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完整构造函数(用于创建包含全量能力的通道元数据)
|
||||
/// </summary>
|
||||
/// <param name="index">物理通道索引</param>
|
||||
/// <param name="name">通道名称</param>
|
||||
/// <param name="supportPtz">是否支持云台</param>
|
||||
/// <param name="supportAudio">是否支持音频输入</param>
|
||||
/// <param name="supportAi">是否支持 AI 分析</param>
|
||||
/// <param name="resolutions">支持的分辨率列表</param>
|
||||
public ChannelMetadata(
|
||||
int index,
|
||||
string name,
|
||||
bool supportPtz = false,
|
||||
bool supportAudio = false,
|
||||
bool supportAi = false,
|
||||
IEnumerable<string>? resolutions = null)
|
||||
{
|
||||
ChannelIndex = index;
|
||||
Name = name;
|
||||
SupportPtz = supportPtz;
|
||||
SupportAudioIn = supportAudio;
|
||||
SupportAiAnalysis = supportAi;
|
||||
SupportedResolutions = new ReadOnlyCollection<string>(resolutions?.ToList() ?? new List<string>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
176
SHH.CameraSdk/Abstractions/Models/DeviceMetadata.cs
Normal file
176
SHH.CameraSdk/Abstractions/Models/DeviceMetadata.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 增强型设备元数据中心 (V3.3.1 修复版)
|
||||
/// 核心职责:
|
||||
/// <para>1. 封装设备的硬件参数、通道能力、功能集,提供能力自发现</para>
|
||||
/// <para>2. 支持元数据同步与差异对比,指导上层模块执行差异化处理</para>
|
||||
/// <para>3. 存储运维指标与 SDK 原生句柄,支撑故障诊断与性能调优</para>
|
||||
/// 设计特性:只读优先,通过版本号标记同步状态,避免并发修改冲突
|
||||
/// </summary>
|
||||
public class DeviceMetadata
|
||||
{
|
||||
#region --- 1. 设备级身份信息 (Identity) ---
|
||||
|
||||
/// <summary> 设备型号名称(如 DS-2CD3T47G2-LIU) </summary>
|
||||
public string ModelName { get; init; } = "Unknown";
|
||||
|
||||
/// <summary> 设备唯一序列号(全局唯一,用于设备溯源) </summary>
|
||||
public string SerialNumber { get; init; } = string.Empty;
|
||||
|
||||
/// <summary> 固件/系统版本号(用于判断 SDK 兼容性) </summary>
|
||||
public string FirmwareVersion { get; init; } = string.Empty;
|
||||
|
||||
/// <summary> 所属厂商/品牌(决定驱动适配逻辑) </summary>
|
||||
public DeviceBrand Brand { get; init; } = DeviceBrand.Unknown;
|
||||
|
||||
/// <summary> 元数据版本号(本地刷新计数,每次同步自增) </summary>
|
||||
public long Version { get; private set; }
|
||||
|
||||
/// <summary> 最后同步时间(标记元数据的最新有效时刻) </summary>
|
||||
public DateTime LastSyncedAt { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 级联能力模型 (Cascaded Capabilities) ---
|
||||
|
||||
private readonly ReadOnlyCollection<ChannelMetadata> _channels;
|
||||
|
||||
/// <summary> 通道元数据集合(只读,防止外部篡改) </summary>
|
||||
public ReadOnlyCollection<ChannelMetadata> Channels
|
||||
{
|
||||
get => _channels;
|
||||
init => _channels = value ?? new ReadOnlyCollection<ChannelMetadata>(new List<ChannelMetadata>());
|
||||
}
|
||||
|
||||
/// <summary> 设备总通道数量(IPC 通常为 1,NVR 为接入路数) </summary>
|
||||
public int ChannelCount => Channels.Count;
|
||||
|
||||
/// <summary> 索引器:通过通道号快速获取对应通道的能力描述 </summary>
|
||||
/// <param name="channelIndex">物理通道号</param>
|
||||
/// <returns>通道元数据 / 不存在则返回 null</returns>
|
||||
public ChannelMetadata? this[int channelIndex] =>
|
||||
Channels.FirstOrDefault(c => c.ChannelIndex == channelIndex);
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3. 运维指标与非托管句柄 ---
|
||||
|
||||
/// <summary> 设备实时健康度字典(如 CPU 使用率、温度、内存占用等) </summary>
|
||||
public Dictionary<string, object> HealthMetrics { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 厂商 SDK 原始句柄/结构体快照
|
||||
/// <para>注意事项:</para>
|
||||
/// <para>1. 标记 [JsonIgnore] 防止序列化非托管指针导致程序崩溃</para>
|
||||
/// <para>2. 仅用于驱动层与 SDK 交互,上层业务禁止直接操作</para>
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public object? NativeHandle { get; init; }
|
||||
|
||||
/// <summary> 厂商扩展标签(如设备位置、安装时间、责任人等自定义信息) </summary>
|
||||
public Dictionary<string, string> Tags { get; init; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 4. 构造与同步 (Constructor & Sync) ---
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造函数(用于 BaseVideoSource 初始状态,无通道数据)
|
||||
/// </summary>
|
||||
public DeviceMetadata() : this(Enumerable.Empty<ChannelMetadata>()) { }
|
||||
|
||||
/// <summary>
|
||||
/// 完整构造函数(初始化通道元数据集合)
|
||||
/// </summary>
|
||||
/// <param name="channels">通道元数据列表</param>
|
||||
public DeviceMetadata(IEnumerable<ChannelMetadata> channels)
|
||||
{
|
||||
// 转换为只读集合,确保通道数据不可变
|
||||
_channels = new ReadOnlyCollection<ChannelMetadata>(channels?.ToList() ?? new List<ChannelMetadata>());
|
||||
// 标记初始同步状态
|
||||
MarkSynced();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记元数据同步完成
|
||||
/// <para>作用:更新版本号与同步时间,用于差异对比的基准判断</para>
|
||||
/// </summary>
|
||||
public void MarkSynced()
|
||||
{
|
||||
Version++;
|
||||
LastSyncedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 5. 业务逻辑辅助方法 (Business Helpers) ---
|
||||
|
||||
/// <summary>
|
||||
/// 校验动态流配置的合法性(基于设备能力)
|
||||
/// </summary>
|
||||
/// <param name="options">待校验的动态配置项</param>
|
||||
/// <param name="errorMessage">校验失败时的详细原因</param>
|
||||
/// <returns>合法返回 true,非法返回 false</returns>
|
||||
public bool ValidateOptions(DynamicStreamOptions options, out string errorMessage)
|
||||
{
|
||||
errorMessage = string.Empty;
|
||||
if (options == null) return true;
|
||||
|
||||
// 示例校验规则:云台控制权限校验
|
||||
if (options.VendorExtensions?.ContainsKey("PtzAction") == true
|
||||
&& !Channels.Any(c => c.SupportPtz))
|
||||
{
|
||||
errorMessage = "该设备物理硬件不支持云台控制功能";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 可扩展其他校验规则:如分辨率合法性、码流类型支持性等
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 元数据差异比对逻辑(用于 BaseVideoSource.RefreshMetadataAsync 方法)
|
||||
/// </summary>
|
||||
/// <param name="other">最新拉取的设备元数据</param>
|
||||
/// <returns>元数据差异描述符</returns>
|
||||
public MetadataDiff CompareWith(DeviceMetadata other)
|
||||
{
|
||||
// 入参防护:对比对象为空则返回无差异
|
||||
if (other == null) return MetadataDiff.None;
|
||||
|
||||
return new MetadataDiff
|
||||
{
|
||||
// 1. 基础信息变更:任意通道名称变化则标记
|
||||
NameChanged = this.Channels.Any(c =>
|
||||
{
|
||||
var targetChannel = other[c.ChannelIndex];
|
||||
return targetChannel != null && targetChannel.Name != c.Name;
|
||||
}),
|
||||
|
||||
// 2. 能力集变更:PTZ/音频/AI 功能支持状态变化则标记
|
||||
CapabilityChanged = this.Channels.Any(c =>
|
||||
{
|
||||
var targetChannel = other[c.ChannelIndex];
|
||||
if (targetChannel == null) return false;
|
||||
|
||||
return targetChannel.SupportPtz != c.SupportPtz
|
||||
|| targetChannel.SupportAudioIn != c.SupportAudioIn
|
||||
|| targetChannel.SupportAiAnalysis != c.SupportAiAnalysis;
|
||||
}),
|
||||
|
||||
// 3. 致命变更:品牌不一致或通道数量变化(设备更换/替换场景)
|
||||
IsMajorChange = this.Brand != other.Brand || this.ChannelCount != other.ChannelCount,
|
||||
|
||||
// 4. 分辨率配置变更:任意通道的分辨率档位数量变化则标记
|
||||
ResolutionProfilesChanged = this.Channels.Any(c =>
|
||||
{
|
||||
var targetChannel = other[c.ChannelIndex];
|
||||
return targetChannel != null
|
||||
&& targetChannel.SupportedResolutions.Count != c.SupportedResolutions.Count;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
120
SHH.CameraSdk/Abstractions/Models/DynamicStreamOptions.cs
Normal file
120
SHH.CameraSdk/Abstractions/Models/DynamicStreamOptions.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 视频流动态配置项(运行时可调整参数容器)
|
||||
/// 核心职责:承载无需重启流即可动态调整的视频参数,支持局部更新
|
||||
/// 核心特性:
|
||||
/// <para>1. Nullable 模式:仅非空字段会触发参数更新,避免全量重置导致的性能抖动</para>
|
||||
/// <para>2. 分类管理:按画面策略、帧率控制、传输输出、厂商扩展划分参数,逻辑清晰</para>
|
||||
/// <para>3. 空值检查:通过 IsEmpty 判断是否存在有效配置,避免无效底层 SDK 调用</para>
|
||||
/// </summary>
|
||||
public class DynamicStreamOptions
|
||||
{
|
||||
#region --- 1. 画面策略 (Resolution & Scaling) ---
|
||||
|
||||
/// <summary>
|
||||
/// 目标输出宽度(像素)
|
||||
/// <para>Nullable 规则:null = 保持当前配置;非 null = 触发图像缩放逻辑</para>
|
||||
/// <para>注意事项:建议与 TargetHeight 成对设置,避免画面比例失衡</para>
|
||||
/// </summary>
|
||||
public int? TargetWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标输出高度(像素)
|
||||
/// <para>Nullable 规则:null = 保持当前配置;非 null = 触发图像缩放逻辑</para>
|
||||
/// <para>协作关系:与 TargetWidth 配合使用,若仅设置其一,会按原始宽高比自动计算另一值</para>
|
||||
/// </summary>
|
||||
public int? TargetHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图像放大开关
|
||||
/// <para>Nullable 规则:null = 保持当前策略;true = 允许放大;false = 禁止放大</para>
|
||||
/// <para>性能影响:禁止放大可节省插值计算资源,适合低性能设备</para>
|
||||
/// </summary>
|
||||
public bool? AllowEnlarge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图像缩小开关
|
||||
/// <para>Nullable 规则:null = 保持当前策略;true = 允许缩小;false = 禁止缩小</para>
|
||||
/// <para>适用场景:禁止缩小可保留原始画质,适合需要高清分析的场景</para>
|
||||
/// </summary>
|
||||
public bool? AllowShrink { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 频率控制 (Frame Rate Control) ---
|
||||
|
||||
/// <summary>
|
||||
/// 目标渲染/显示帧率(fps)
|
||||
/// <para>Nullable 规则:null = 不修改;0 = 跟随原始流速度;非 0 = 强制限定显示帧率</para>
|
||||
/// <para>作用域:仅影响 UI 预览层,不会改变底层码流的采集帧率</para>
|
||||
/// </summary>
|
||||
public int? TargetDisplayFps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标 AI 分析帧率(fps)
|
||||
/// <para>Nullable 规则:null = 不修改;非 null = 限定算法处理的输入帧率</para>
|
||||
/// <para>性能优化:降低此值可显著减少高分辨率下的 GPU/CPU 负荷(如 4K 从 30fps 降到 5fps)</para>
|
||||
/// </summary>
|
||||
public int? TargetAnalyzeFps { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3. 传输与输出 (Transmission & Output) ---
|
||||
|
||||
/// <summary>
|
||||
/// Web 推流开关
|
||||
/// <para>Nullable 规则:null = 保持当前状态;true = 启动推流;false = 停止推流</para>
|
||||
/// <para>协作组件:开启后会将处理后的视频帧推送到流媒体服务器(如 FFmpeg/RTSP 服务器)</para>
|
||||
/// </summary>
|
||||
public bool? EnableStreamOutput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 渲染窗体句柄
|
||||
/// <para>Nullable 规则:null = 保持当前窗口;非 null = 切换到新窗口渲染</para>
|
||||
/// <para>适用场景:支持视频窗口拖拽、多显示器切换等交互操作</para>
|
||||
/// </summary>
|
||||
public IntPtr? RenderHandle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 码流类型切换
|
||||
/// <para>取值规则:0 = 主码流(高清/大带宽);1 = 子码流(标清/低延迟);2 = 第三码流</para>
|
||||
/// <para>Nullable 规则:null = 不切换;非 null = 执行码流切换</para>
|
||||
/// <para>注意事项:切换会销毁并重建预览句柄,可能导致短暂的画面中断</para>
|
||||
/// </summary>
|
||||
public int? StreamType { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 4. 厂商扩展 (Vendor Specific) ---
|
||||
|
||||
/// <summary>
|
||||
/// 厂商特有参数扩展字典
|
||||
/// <para>用途:存放无法标准化的品牌专属功能参数</para>
|
||||
/// <para>示例:海康 "FocusMode"=Auto/Manual;大华 "SmartH264"=true/false</para>
|
||||
/// <para>注意事项:键值对需与对应厂商 SDK 的参数名一致,否则无效</para>
|
||||
/// </summary>
|
||||
public Dictionary<string, object> VendorExtensions { get; set; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 5. 配置有效性检查 ---
|
||||
|
||||
/// <summary>
|
||||
/// 逻辑空检查:判断当前配置包是否包含任何有效修改项
|
||||
/// <para>使用场景:调用 SDK 前判断,避免无意义的底层调用,提升性能</para>
|
||||
/// </summary>
|
||||
public bool IsEmpty =>
|
||||
TargetWidth is null &&
|
||||
TargetHeight is null &&
|
||||
AllowEnlarge is null &&
|
||||
AllowShrink is null &&
|
||||
TargetDisplayFps is null &&
|
||||
TargetAnalyzeFps is null &&
|
||||
EnableStreamOutput is null &&
|
||||
RenderHandle is null &&
|
||||
StreamType is null &&
|
||||
VendorExtensions.Count == 0;
|
||||
|
||||
#endregion
|
||||
}
|
||||
53
SHH.CameraSdk/Abstractions/Models/MetadataDiff.cs
Normal file
53
SHH.CameraSdk/Abstractions/Models/MetadataDiff.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 元数据变更差异描述符(只读结构体)
|
||||
/// 核心职责:对比设备当前运行元数据与最新拉取元数据的差异,明确变更类型及业务影响
|
||||
/// 协作场景:指导上层模块执行差异化处理(如仅刷新UI、重启流、调整功能按钮)
|
||||
/// </summary>
|
||||
public readonly struct MetadataDiff
|
||||
{
|
||||
#region --- 差异类型属性 (Change Type Properties) ---
|
||||
|
||||
/// <summary>
|
||||
/// 设备基础描述信息变更(如名称、位置)
|
||||
/// 业务影响:仅需刷新 UI 显示文字,无需中断当前流
|
||||
/// </summary>
|
||||
public bool NameChanged { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备能力集变更(如新增/移除对讲、截图功能)
|
||||
/// 业务影响:需调整 UI 功能按钮的可用性,无需中断流
|
||||
/// </summary>
|
||||
public bool CapabilityChanged { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 致命/破坏性变更(如设备型号、编码格式变更)
|
||||
/// 业务影响:必须销毁当前流实例并重启,否则会导致流异常或崩溃
|
||||
/// </summary>
|
||||
public bool IsMajorChange { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 分辨率/帧率档位列表变更
|
||||
/// 业务影响:需重新校验当前流参数是否合法,非法则自动降级到可用档位
|
||||
/// </summary>
|
||||
public bool ResolutionProfilesChanged { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 全局变更标识:是否存在任何类型的元数据变更
|
||||
/// 业务用途:快速判断是否需要执行后续差异化处理逻辑
|
||||
/// </summary>
|
||||
public bool HasChanges => NameChanged || CapabilityChanged || IsMajorChange || ResolutionProfilesChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 快捷实例 (Quick Instances) ---
|
||||
|
||||
/// <summary>
|
||||
/// 无变更状态的快捷实例
|
||||
/// 业务用途:元数据刷新后无变化时直接返回,避免重复创建空对象
|
||||
/// </summary>
|
||||
public static MetadataDiff None => new();
|
||||
|
||||
#endregion
|
||||
}
|
||||
57
SHH.CameraSdk/Abstractions/Models/ResolutionProfile.cs
Normal file
57
SHH.CameraSdk/Abstractions/Models/ResolutionProfile.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 视频分辨率档位描述符(Record 类型,不可变对象)
|
||||
/// 核心职责:定义相机通道在特定编码格式下支持的分辨率、帧率上限及友好描述
|
||||
/// 协作场景:
|
||||
/// <para>1. 前端界面:展示清晰度选择下拉列表</para>
|
||||
/// <para>2. 配置校验:下发分辨率前判断是否符合硬件能力,防止超限黑屏</para>
|
||||
/// <para>3. 性能评估:通过总像素量计算编码/解码的计算负载与带宽压力</para>
|
||||
/// </summary>
|
||||
/// <param name="Width">画面像素宽度(如 1920、3840)</param>
|
||||
/// <param name="Height">画面像素高度(如 1080、2160)</param>
|
||||
/// <param name="MaxFps">该分辨率下硬件支持的最大输出帧率(防止超限配置)</param>
|
||||
/// <param name="Description">档位友好描述(如 "高清(1080P/H.265)")</param>
|
||||
public record ResolutionProfile(
|
||||
int Width,
|
||||
int Height,
|
||||
int MaxFps,
|
||||
string Description
|
||||
)
|
||||
{
|
||||
#region --- 计算属性 (Derived Properties) ---
|
||||
|
||||
/// <summary>
|
||||
/// 当前档位的总像素量
|
||||
/// 业务用途:衡量编码/解码的计算负载、网络传输的带宽压力
|
||||
/// 计算公式:Width * Height
|
||||
/// </summary>
|
||||
public long TotalPixels => (long)Width * Height;
|
||||
|
||||
/// <summary>
|
||||
/// 当前档位的屏幕宽高比
|
||||
/// 业务用途:前端渲染容器(WinForm/WPF/Web)自动调整比例,避免画面拉伸变形
|
||||
/// 保护逻辑:高度为 0 时返回 0,防止除零异常
|
||||
/// </summary>
|
||||
public double AspectRatio => Height == 0 ? 0 : (double)Width / Height;
|
||||
|
||||
/// <summary>
|
||||
/// 判断当前档位是否属于高清范畴(行业标准:720P 及以上,即 1280x720 分辨率)
|
||||
/// 业务用途:前端分类展示、带宽策略选择
|
||||
/// </summary>
|
||||
public bool IsHighDefinition => Width >= 1280 && Height >= 720;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 重写方法 (Overridden Methods) ---
|
||||
|
||||
/// <summary>
|
||||
/// 格式化分辨率档位的显示文本
|
||||
/// 输出格式:友好描述 (宽x高@最大帧率fps)
|
||||
/// 示例:高清(1080P/H.265) (1920x1080@30fps)
|
||||
/// </summary>
|
||||
/// <returns>格式化的显示字符串</returns>
|
||||
public override string ToString() => $"{Description} ({Width}x{Height}@{MaxFps}fps)";
|
||||
|
||||
#endregion
|
||||
}
|
||||
80
SHH.CameraSdk/Abstractions/Models/StatusChangedEventArgs.cs
Normal file
80
SHH.CameraSdk/Abstractions/Models/StatusChangedEventArgs.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 视频源状态变更事件参数
|
||||
/// 核心职责:封装状态迁移的完整上下文信息,支撑三大业务场景
|
||||
/// <para>1. UI 层:实时反馈设备状态、显示错误提示</para>
|
||||
/// <para>2. 诊断层:记录异常堆栈、SDK 错误码,辅助问题定位</para>
|
||||
/// <para>3. 运维层:触发自动重连、告警推送等自动化决策</para>
|
||||
/// </summary>
|
||||
public class StatusChangedEventArgs : EventArgs
|
||||
{
|
||||
#region --- 事件核心属性 (Event Core Properties) ---
|
||||
|
||||
/// <summary>
|
||||
/// 变更后的目标状态
|
||||
/// 业务用途:
|
||||
/// 1. UI 层:控制状态图标颜色(如 Playing→绿色、Faulted→红色)
|
||||
/// 2. 运维层:状态为 Reconnecting 时触发重连策略调整
|
||||
/// </summary>
|
||||
public VideoSourceStatus NewStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态描述文本(可读)
|
||||
/// 业务用途:
|
||||
/// 1. UI 层:直接显示在状态栏或操作日志面板
|
||||
/// 2. 日志层:写入业务日志,便于人工排查
|
||||
/// </summary>
|
||||
public string Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联的异常对象(可选)
|
||||
/// 业务用途:仅当 NewStatus = Faulted 时有效,提供异常堆栈、类型等代码级诊断信息
|
||||
/// </summary>
|
||||
public Exception? Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// SDK 底层原始错误码(可选)
|
||||
/// 业务用途:
|
||||
/// 1. 厂商适配:匹配海康 NET_DVR_GetLastError、大华 SDK 错误码等
|
||||
/// 2. 精准诊断:区分“用户锁定(153)”“密码错误(41)”“网络超时”等根因
|
||||
/// </summary>
|
||||
public int? LastErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更后的新句柄(可选)
|
||||
/// 业务用途:渲染器解绑/重绑场景,监听此值更新窗口句柄绑定关系
|
||||
/// </summary>
|
||||
public IntPtr? NewHandle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更发生的时间戳
|
||||
/// 业务用途:日志时序排序、状态迁移耗时统计
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; } = DateTime.Now;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 构造函数 (Constructor) ---
|
||||
|
||||
/// <summary>
|
||||
/// 初始化状态变更事件参数
|
||||
/// </summary>
|
||||
/// <param name="status">变更后的目标状态</param>
|
||||
/// <param name="msg">可读的状态描述文本</param>
|
||||
/// <param name="ex">可选:关联的异常对象</param>
|
||||
/// <param name="errorCode">可选:SDK 底层错误码</param>
|
||||
public StatusChangedEventArgs(
|
||||
VideoSourceStatus status,
|
||||
string msg,
|
||||
Exception? ex = null,
|
||||
int? errorCode = null)
|
||||
{
|
||||
NewStatus = status;
|
||||
Message = msg;
|
||||
Exception = ex;
|
||||
LastErrorCode = errorCode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
150
SHH.CameraSdk/Abstractions/Models/VideoSourceConfig.cs
Normal file
150
SHH.CameraSdk/Abstractions/Models/VideoSourceConfig.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 视频源基础配置对象 (V3.3.1 修复版)
|
||||
/// 核心职责:定义建立相机物理连接所需的所有标准化参数与厂商扩展参数
|
||||
/// 关键修复:
|
||||
/// <para>1. [Fix Bug U: 配置漂移] 驱动层接收配置时需创建副本,防止外部修改导致的连接异常</para>
|
||||
/// <para>2. 强化配置有效性校验,提前拦截非法参数</para>
|
||||
/// 注意事项:此类为引用类型,传递时建议使用深拷贝
|
||||
/// </summary>
|
||||
public class VideoSourceConfig
|
||||
{
|
||||
#region --- 1. 核心连接配置 (Core Connection Configurations) ---
|
||||
|
||||
/// <summary> 业务系统唯一标识(对应数据库自增ID,全局唯一) </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary> 设备显示名称(如:北大门-枪机01,用于前端展示) </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> 设备品牌(决定加载对应的驱动实现类) </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public DeviceBrand Brand { get; set; } = DeviceBrand.Unknown;
|
||||
|
||||
/// <summary> 设备 IP 地址或域名(如 192.168.1.100 或 camera.example.com) </summary>
|
||||
public string IpAddress { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> 设备端口号(厂商默认值:海康8000、大华37777、RTSP 554) </summary>
|
||||
public ushort Port { get; set; }
|
||||
|
||||
/// <summary> 设备登录用户名(默认值:admin) </summary>
|
||||
public string Username { get; set; } = "admin";
|
||||
|
||||
/// <summary> 设备登录密码(默认空字符串,需根据实际设备配置) </summary>
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> 渲染句柄(可选):用于硬解码时直接绑定显示窗口,提升渲染性能 </summary>
|
||||
public IntPtr RenderHandle { get; set; } = IntPtr.Zero;
|
||||
|
||||
/// <summary> 物理通道号(IPC 通常为 1;NVR 对应接入的摄像头通道索引) </summary>
|
||||
public int ChannelIndex { get; set; } = 1;
|
||||
|
||||
/// <summary> 默认码流类型(0 = 主码流(高清),1 = 子码流(低带宽)) </summary>
|
||||
public int StreamType { get; set; } = 0;
|
||||
|
||||
/// <summary> 传输协议(TCP/UDP/Multicast,默认 TCP 保证可靠性) </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public TransportProtocol Transport { get; set; } = TransportProtocol.Tcp;
|
||||
|
||||
/// <summary> 连接超时时间(毫秒,默认 5000ms = 5秒) </summary>
|
||||
public int ConnectionTimeoutMs { get; set; } = 5000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 厂商扩展配置 (Vendor-Specific Extensions) ---
|
||||
|
||||
/// <summary>
|
||||
/// 厂商扩展参数字典
|
||||
/// 用途:存储无法标准化的品牌专属参数
|
||||
/// 示例:
|
||||
/// <code>
|
||||
/// {
|
||||
/// "RtspPath": "/h264/ch1/main/av_stream",
|
||||
/// "HikLoginMode": "ISAPI",
|
||||
/// "DaHuaStreamProtocol": "HTTP"
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public Dictionary<string, string> VendorArguments { get; set; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3. 配置工具方法 (Configuration Utility Methods) ---
|
||||
|
||||
/// <summary>
|
||||
/// 配置有效性校验:检查核心参数是否合法,非法则抛出异常
|
||||
/// 作用:提前拦截无效配置,避免驱动层连接时出现未知错误
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">核心参数非法时抛出</exception>
|
||||
public void Validate()
|
||||
{
|
||||
if (Id <= 0)
|
||||
throw new ArgumentException("配置ID必须为正整数", nameof(Id));
|
||||
if (string.IsNullOrWhiteSpace(IpAddress))
|
||||
throw new ArgumentException("IP地址不能为空", nameof(IpAddress));
|
||||
if (Port == 0)
|
||||
throw new ArgumentException("端口号必须为有效数值", nameof(Port));
|
||||
if (Brand == DeviceBrand.Unknown)
|
||||
throw new ArgumentException("必须指定设备品牌", nameof(Brand));
|
||||
if (ChannelIndex <= 0)
|
||||
throw new ArgumentException("通道号必须为正整数", nameof(ChannelIndex));
|
||||
if (ConnectionTimeoutMs <= 0)
|
||||
throw new ArgumentException("连接超时时间必须大于0", nameof(ConnectionTimeoutMs));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成设备唯一连接指纹
|
||||
/// 用途:用于连接池去重、缓存键、日志标识等场景
|
||||
/// 格式:{Brand}://{Username}@{IpAddress}:{Port}/{ChannelIndex}
|
||||
/// </summary>
|
||||
/// <returns>唯一连接指纹字符串</returns>
|
||||
public string GetConnectionKey()
|
||||
{
|
||||
// 使用 StringBuilder 提升拼接性能,避免频繁创建字符串
|
||||
return new StringBuilder()
|
||||
.Append(Brand)
|
||||
.Append("://")
|
||||
.Append(Username)
|
||||
.Append('@')
|
||||
.Append(IpAddress)
|
||||
.Append(':')
|
||||
.Append(Port)
|
||||
.Append('/')
|
||||
.Append(ChannelIndex)
|
||||
.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建配置对象的深拷贝(防止外部修改导致配置漂移)
|
||||
/// </summary>
|
||||
/// <returns>新的配置副本</returns>
|
||||
public VideoSourceConfig DeepCopy()
|
||||
{
|
||||
var copy = new VideoSourceConfig
|
||||
{
|
||||
Id = this.Id,
|
||||
Name = this.Name,
|
||||
Brand = this.Brand,
|
||||
IpAddress = this.IpAddress,
|
||||
Port = this.Port,
|
||||
Username = this.Username,
|
||||
Password = this.Password,
|
||||
RenderHandle = this.RenderHandle,
|
||||
ChannelIndex = this.ChannelIndex,
|
||||
StreamType = this.StreamType,
|
||||
Transport = this.Transport,
|
||||
ConnectionTimeoutMs = this.ConnectionTimeoutMs
|
||||
};
|
||||
|
||||
// 深拷贝扩展参数字典
|
||||
foreach (var kvp in this.VendorArguments)
|
||||
{
|
||||
copy.VendorArguments.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user