海康摄像头取流示例初始签入

This commit is contained in:
2025-12-26 03:18:21 +08:00
parent 86db2cf6b4
commit 6281f4248e
44 changed files with 5588 additions and 0 deletions

View 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
}

View 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
}

View 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
}

View 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 → 上限 120s2分钟
/// </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
}