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

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

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

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

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
}

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

View 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>
/// 是否支持云台控制PTZPan/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
}

View 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 通常为 1NVR 为接入路数) </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
}

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

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

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

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

View 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 通常为 1NVR 对应接入的摄像头通道索引) </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
}