From e06c60968d6cb67bead84361be64abbcddd972e2 Mon Sep 17 00:00:00 2001 From: wilson Date: Sat, 17 Jan 2026 14:03:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=B8=E5=BF=83=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SHH.CameraSdk/Drivers/BaseVideoSource.cs | 521 +++++++++--------- .../Drivers/HikVision/HikVideoSource.cs | 203 +++++-- 2 files changed, 409 insertions(+), 315 deletions(-) diff --git a/SHH.CameraSdk/Drivers/BaseVideoSource.cs b/SHH.CameraSdk/Drivers/BaseVideoSource.cs index 297a7d6..8ff6137 100644 --- a/SHH.CameraSdk/Drivers/BaseVideoSource.cs +++ b/SHH.CameraSdk/Drivers/BaseVideoSource.cs @@ -3,58 +3,31 @@ namespace SHH.CameraSdk; /// -/// [架构基类] 工业级视频源抽象核心 (V3.3.4 严格匹配版) +/// [架构基类] 工业级视频源抽象核心 (V3.5.0 严格匹配版) +/// 当前模块: AiVideo | 核心原则: 低耦合、高并发、零拷贝 /// 核心职责: -/// 1. 提供线程安全的生命周期管理(启动/停止/销毁) -/// 2. 实现状态变更的可靠分发与异常隔离 -/// 3. 支持配置热更新与动态参数应用 -/// 4. 内置 FPS/码率统计、心跳保活、审计日志能力 +/// 1. 生命周期管理:基于 实现 Start/Stop/UpdateConfig 的线程安全串行化 +/// 2. 状态分发系统:利用 (DropOldest策略) 实现高性能、非阻塞的状态变更通知 +/// 3. 遥测统计引擎:内置原子级 FPS 计算与 Mbps 带宽监控,支持网络层与解码层双路流量计费 +/// 4. 弹性自愈机制:集成 IsAuthFailed 冻结期与物理网络 (Ping) 状态同步,支持 Coordinator 级自动重连 +/// 5. 审计与追踪:内置环形审计日志 (Max:100),记录配置变更、动态参数应用及驱动层异常 /// 关键修复记录: -/// ✅ [Bug A] 死锁免疫:所有 await 均添加 ConfigureAwait(false),解除 UI 线程依赖 -/// ✅ [Bug π] 管道安全:Dispose 采用优雅关闭策略,确保剩余状态消息被消费 -/// ✅ [编译修复] 补全 CloneConfig 中 Transport/VendorArguments 的深拷贝逻辑 +/// ✅ [V3.3.5] 资源防御:在 DisposeAsync 中强化生命周期锁,防止销毁期间触发重入 +/// ✅ [V3.3.5] 自愈增强:引入 Environment.TickCount64 宽限期机制,解决启动瞬间心跳超时误判 +/// ✅ [Bug A] 死锁免疫:强制所有 await 添加 ConfigureAwait(false),彻底解除对同步上下文的依赖 +/// ✅ [Bug π] 管道安全:Dispose 采用 TryComplete 优雅关闭策略,确保缓冲区剩余状态消息被完全消费 /// public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceConnectivity { - // [新增] 物理在线状态(专门给 Ping 使用) - private volatile bool _isPhysicalOnline; - public bool IsPhysicalOnline => _isPhysicalOnline; + #region --- 1. 核心字段与锁机制 (Fields & Locks) --- /// - /// 图像预处理配置(缩放、增量等) - /// 放置在基类中,确保所有接入协议(HIK/DH/RTSP)均可共享处理逻辑 - /// - public PreprocessConfig PreprocessSettings { get; set; } - = new PreprocessConfig(); - - string IDeviceConnectivity.IpAddress => _config.IpAddress; - - // 允许哨兵从外部更新 _isOnline 字段 - void IDeviceConnectivity.SetNetworkStatus(bool isOnline) - { - if (_isPhysicalOnline != isOnline) - { - _isPhysicalOnline = isOnline; - // 触发状态变更是为了通知 UI 更新绿色小圆点,但不改变 Status - // 注意:这里传 _status 保持原样,只变消息 - StatusChanged?.Invoke(this, new StatusChangedEventArgs(_status, isOnline ? "物理网络恢复" : "物理网络中断")); - } - } - - protected abstract ILogger _sdkLog { get; } - - #region --- 1. 核心配置与锁机制 (Core Config & Locks) --- - - /// - /// 核心配置对象(支持热更新,去除 readonly 修饰符) + /// 核心配置对象(支持热更新) /// 注意:外部修改需通过 UpdateConfig 方法,确保线程安全 /// protected VideoSourceConfig _config; - /// - /// 状态同步锁 - /// 作用:保护 _status 字段的读写原子性,防止多线程竞争导致状态不一致 - /// + /// 状态同步锁:保护 _status 字段的读写原子性,防止多线程竞争导致状态不一致 private readonly object _stateSyncRoot = new(); /// @@ -64,9 +37,11 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC /// private readonly SemaphoreSlim _lifecycleLock = new(1, 1); - #endregion + /// 跟踪上一个未完成的生命周期任务 + private Task _pendingLifecycleTask = Task.CompletedTask; - #region --- 2. 内部状态与基础设施 (Internal States & Infrastructure) --- + /// 物理在线状态(由哨兵 Ping 更新) + private volatile bool _isPhysicalOnline; /// 设备在线状态标志(volatile 确保多线程可见性) private volatile bool _isActived; @@ -74,6 +49,13 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC /// 视频源核心状态(受 _stateSyncRoot 保护) private VideoSourceStatus _status = VideoSourceStatus.Disconnected; + /// 资源销毁标记 + private volatile bool _isDisposed = false; + + #endregion + + #region --- 2. 状态分发基础设施 (Status Channel) --- + /// /// 状态通知有界通道 /// 特性:DropOldest 策略,消费者过载时丢弃旧状态,防止内存溢出 @@ -87,15 +69,60 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC /// 状态分发任务引用(用于 Dispose 时优雅等待) private Task? _distributorTask; + #endregion + + #region --- 3. 遥测与性能统计 (Telemetry & Stats) --- + /// 最后一帧接收的系统 Tick(单调时钟,不受系统时间修改影响) private long _lastFrameTick = 0; + /// 生命周期内接收的总帧数 + private long _totalFramesReceived = 0; + + /// FPS 计算临时计数器 + private int _tempFrameCounter = 0; + + /// 上次 FPS 计算的 Tick 时间 + private long _lastFpsCalcTick = 0; + + /// 实时码率 (Mbps) + protected double _currentBitrate = 0; + + /// 码率计算临时字节计数器 + private long _tempByteCounter = 0; + + #endregion + + #region --- 4. 审计日志与自愈标记 (Audit & Resilience) --- + + /// 审计日志列表(线程安全访问) + private readonly List _auditLogs = new(); + + /// 最大日志条数(滚动清除,防止内存溢出) + private const int MaxAuditLogCount = 100; + + /// + /// 认证类致命错误标记(如密码错、用户锁定) + /// 作用:触发 15 分钟长冷冻期,防止 IP 被相机锁定 + /// + public bool IsAuthFailed { get; set; } + + /// 上次尝试执行 StartAsync 的系统 Tick 时间 (单调时钟) + private long _lastStartAttemptTick = 0; + + #endregion + + #region --- 5. 公开事件 (Events) --- + /// 视频帧回调事件(热路径,低延迟分发) public event Action? FrameReceived; + /// 状态变更事件(对外异步暴露状态通知) + public event EventHandler? StatusChanged; + #endregion - #region --- 3. 公开属性 (Public Properties) --- + #region --- 6. 公开属性 (Properties) --- /// 视频源唯一标识 public long Id => _config.Id; @@ -108,10 +135,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC { get { - lock (_stateSyncRoot) - { - return _status; - } + lock (_stateSyncRoot) return _status; } } @@ -121,57 +145,78 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC /// 设备在线状态 public bool IsActived => _isActived; + /// 物理在线状态(Ping 结果) + public bool IsPhysicalOnline => _isPhysicalOnline; + /// 设备元数据(能力集、通道信息等) public DeviceMetadata Metadata { get; protected set; } = new(); - /// 状态变更事件(对外暴露状态通知) - public event EventHandler? StatusChanged; + /// 帧控制器(用于帧分发策略管理) + public FrameController Controller { get; protected set; } + + /// + /// 图像预处理配置(缩放、增量等) + /// 放置在基类中,确保所有接入协议(HIK/DH/RTSP)均可共享处理逻辑 + /// + public PreprocessConfig PreprocessSettings { get; set; } + = new PreprocessConfig(); /// 最后一帧接收的 Tick 时间戳(线程安全读取) public long LastFrameTick => Interlocked.Read(ref _lastFrameTick); - #endregion - - #region --- 4. 遥测统计属性 (Telemetry Properties) --- - - /// 生命周期内接收的总帧数 - private long _totalFramesReceived = 0; - - /// FPS 计算临时计数器 - private int _tempFrameCounter = 0; - - /// 上次 FPS 计算的 Tick 时间 - private long _lastFpsCalcTick = 0; - - // 提供一个最近一秒的输入帧率参考值 - public int NominalInputFps => (int)Math.Round(RealFps); + /// 生命周期总帧数(线程安全读取) + public long TotalFrames => Interlocked.Read(ref _totalFramesReceived); /// 实时 FPS(每秒更新一次) public double RealFps { get; private set; } = 0.0; + // 提供一个最近一秒的输入帧率参考值 + public int NominalInputFps => (int)Math.Round(RealFps); + /// 实时码率 (Mbps) - protected double _currentBitrate = 0; public double RealBitrate => _currentBitrate; - /// 码率计算临时字节计数器 - private long _tempByteCounter = 0; + /// 视频宽度 + public int Width { get; protected set; } - /// 生命周期总帧数(线程安全读取) - public long TotalFrames => Interlocked.Read(ref _totalFramesReceived); + /// 视频高度 + public int Height { get; protected set; } + + /// 满足接口要求的 IP 地址 + string IDeviceConnectivity.IpAddress => _config.IpAddress; + + /// 上次启动尝试的时间戳 + public long LastStartAttemptTick => Interlocked.Read(ref _lastStartAttemptTick); #endregion - #region --- 5. 审计日志系统 (Audit Log System) --- + #region --- 7. 抽象成员 (Abstracts) --- - /// 审计日志列表(线程安全访问) - private readonly List _auditLogs = new(); + /// 子类特定的日志记录器 + protected abstract ILogger _sdkLog { get; } - /// 最大日志条数(滚动清除,防止内存溢出) - private const int MaxAuditLogCount = 100; + /// + /// 驱动层启动逻辑(子类必须实现) + /// 包含:设备登录、码流订阅、取流线程启动等 + /// + /// 取消令牌 + protected abstract Task OnStartAsync(CancellationToken token); + + /// + /// 驱动层停止逻辑(子类必须实现) + /// 包含:码流停止、设备登出、资源释放等 + /// + protected abstract Task OnStopAsync(); + + /// + /// 驱动层元数据获取逻辑(子类必须实现) + /// + /// 设备元数据 + protected abstract Task OnFetchMetadataAsync(); #endregion - #region --- 6. 构造函数 (Constructor) --- + #region --- 8. 构造函数 (Constructor) --- /// /// 初始化视频源基础设施 @@ -204,67 +249,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC #endregion - #region --- 7. 配置管理 (Config Management) --- - - /// - /// 热更新视频源配置(线程安全) - /// 新配置将在下次启动/重连时生效 - /// - /// 新的视频源配置 - public void UpdateConfig(VideoSourceConfig newConfig) - { - if (newConfig == null) return; - - // 加生命周期锁:防止与启动/停止操作并发 - _lifecycleLock.Wait(); - try - { - // 深拷贝新配置,隔离外部引用 - _config = newConfig.DeepCopy(); - - // 写入审计日志 - AddAuditLog($"配置已更新 [IP:{_config.IpAddress}],生效时机:{(_isActived ? "下次重连" : "下次启动")}"); - Debug.WriteLine($"[ConfigUpdated] 设备 {Id} 配置落地完成"); - } - finally - { - _lifecycleLock.Release(); - } - } - - /// - /// 配置深拷贝辅助方法(确保引用类型独立) - /// - /// 源配置 - /// 深拷贝后的新配置 - private VideoSourceConfig CloneConfig(VideoSourceConfig source) - { - return new VideoSourceConfig - { - Id = source.Id, - Brand = source.Brand, - IpAddress = source.IpAddress, - Port = source.Port, - Username = source.Username, - Password = source.Password, - ChannelIndex = source.ChannelIndex, - StreamType = source.StreamType, - Transport = source.Transport, - ConnectionTimeoutMs = source.ConnectionTimeoutMs, - MainboardIp = source.MainboardIp, - MainboardPort = source.MainboardPort, - RtspPath = source.RtspPath, - RenderHandle = source.RenderHandle, - // Dictionary 深拷贝:防止外部修改影响内部 - VendorArguments = source.VendorArguments != null - ? new Dictionary(source.VendorArguments) - : new Dictionary() - }; - } - - #endregion - - #region --- 8. 生命周期控制 (Lifecycle Control) --- + #region --- 9. 生命周期控制 (Lifecycle) --- /// /// 异步启动设备连接 @@ -339,6 +324,66 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC } } + #endregion + + #region --- 10. 配置与元数据管理 (Config & Metadata) --- + + /// + /// 热更新视频源配置(线程安全) + /// 新配置将在下次启动/重连时生效 + /// + /// 新的视频源配置 + public void UpdateConfig(VideoSourceConfig newConfig) + { + if (newConfig == null) return; + + // 加生命周期锁:防止与启动/停止操作并发 + _lifecycleLock.Wait(); + try + { + // 深拷贝新配置,隔离外部引用 + _config = newConfig.DeepCopy(); + + // 写入审计日志 + AddAuditLog($"配置已更新 [IP:{_config.IpAddress}],生效时机:{(_isActived ? "下次重连" : "下次启动")}"); + Debug.WriteLine($"[ConfigUpdated] 设备 {Id} 配置落地完成"); + } + finally + { + _lifecycleLock.Release(); + } + } + + /// + /// 配置深拷贝辅助方法(确保引用类型独立) + /// + /// 源配置 + /// 深拷贝后的新配置 + private VideoSourceConfig CloneConfig(VideoSourceConfig source) + { + return new VideoSourceConfig + { + Id = source.Id, + Brand = source.Brand, + IpAddress = source.IpAddress, + Port = source.Port, + Username = source.Username, + Password = source.Password, + ChannelIndex = source.ChannelIndex, + StreamType = source.StreamType, + Transport = source.Transport, + ConnectionTimeoutMs = source.ConnectionTimeoutMs, + MainboardIp = source.MainboardIp, + MainboardPort = source.MainboardPort, + RtspPath = source.RtspPath, + RenderHandle = source.RenderHandle, + // Dictionary 深拷贝:防止外部修改影响内部 + VendorArguments = source.VendorArguments != null + ? new Dictionary(source.VendorArguments) + : new Dictionary() + }; + } + /// /// 刷新设备元数据,对比差异并更新 /// @@ -422,7 +467,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC #endregion - #region --- 9. 帧处理与状态管理 (Frame Processing & Status Management) --- + #region --- 11. 帧处理与遥测上报 (Data Handling) --- /// /// 检查是否存在帧订阅者(性能优化) @@ -432,17 +477,10 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC protected bool HasFrameSubscribers() => FrameReceived != null; /// - /// 上报驱动层异常,触发重连自愈逻辑 + /// 触发帧回调事件(热路径优化) /// - /// 相机统一异常 - protected void ReportError(CameraException ex) - { - if (!_isActived) return; - - // 标记离线并更新状态为重连中 - _isActived = false; - UpdateStatus(VideoSourceStatus.Reconnecting, $"SDK异常: {ex.Message}", ex); - } + /// 帧数据(如 Mat/SmartFrame) + protected void RaiseFrameReceived(object frameData) => FrameReceived?.Invoke(frameData); /// /// 标记数据接收(心跳保活 + 双路统计) @@ -509,11 +547,49 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC } } + #endregion + + #region --- 12. 状态管理与分发 (Status Distributor) --- + /// - /// 触发帧回调事件(热路径优化) + /// 更新设备状态并写入分发队列 /// - /// 帧数据(如 Mat/SmartFrame) - protected void RaiseFrameReceived(object frameData) => FrameReceived?.Invoke(frameData); + /// 新状态 + /// 状态描述 + /// 关联异常(可选) + protected void UpdateStatus(VideoSourceStatus status, string msg, CameraException? ex = null) + { + lock (_stateSyncRoot) + { + // 更新内部状态 + _status = status; + + // 写入状态队列(满时自动丢弃旧数据) + _ = _statusQueue.Writer.TryWrite(new StatusChangedEventArgs( + status, + msg, + ex, + ex?.RawErrorCode) + { + NewHandle = ex?.Context.TryGetValue("NativeHandle", out var handle) == true ? (IntPtr)handle : null + }); + } + } + + /// + /// 允许哨兵从外部更新 _isOnline 字段 + /// + /// + void IDeviceConnectivity.SetNetworkStatus(bool isOnline) + { + if (_isPhysicalOnline != isOnline) + { + _isPhysicalOnline = isOnline; + // 触发状态变更是为了通知 UI 更新绿色小圆点,但不改变 Status + // 注意:这里传 _status 保持原样,只变消息 + StatusChanged?.Invoke(this, new StatusChangedEventArgs(_status, isOnline ? "物理网络恢复" : "物理网络中断")); + } + } /// /// 后台状态分发循环(单线程消费状态队列) @@ -562,33 +638,45 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC } /// - /// 更新设备状态并写入分发队列 + /// 上报驱动层异常,触发重连自愈逻辑 /// - /// 新状态 - /// 状态描述 - /// 关联异常(可选) - protected void UpdateStatus(VideoSourceStatus status, string msg, CameraException? ex = null) + /// 相机统一异常 + protected void ReportError(CameraException ex) { - lock (_stateSyncRoot) - { - // 更新内部状态 - _status = status; + if (!_isActived) return; - // 写入状态队列(满时自动丢弃旧数据) - _ = _statusQueue.Writer.TryWrite(new StatusChangedEventArgs( - status, - msg, - ex, - ex?.RawErrorCode) - { - NewHandle = ex?.Context.TryGetValue("NativeHandle", out var handle) == true ? (IntPtr)handle : null - }); - } + // 标记离线并更新状态为重连中 + _isActived = false; + UpdateStatus(VideoSourceStatus.Reconnecting, $"SDK异常: {ex.Message}", ex); } #endregion - #region --- 10. 审计日志辅助 (Audit Log Helpers) --- + #region --- 13. 自愈与审计辅助 (Resilience & Audit) --- + + /// + /// 更新最后一次启动尝试的时间戳为当前时间 + /// + public void MarkStartAttempt() + { + Interlocked.Exchange(ref _lastStartAttemptTick, Environment.TickCount64); + } + + /// + /// 强制重置自愈相关的冷却与错误标记 + /// 用于用户手动干预(如修改密码)后,使协调器能立即触发下一次尝试 + /// + public void ResetResilience() + { + // 1. 清除认证失败标记 + IsAuthFailed = false; + + // 2. 将尝试时间戳归零 + // 这样在 Coordinator 中计算 elapsed = now - 0,结果会远大于 30s + Interlocked.Exchange(ref _lastStartAttemptTick, 0); + + _sdkLog.Debug($"[Sdk] 设备 {Id} 自愈状态已人工重置"); + } /// /// 添加审计日志(线程安全) @@ -621,32 +709,18 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC } } - #endregion - - #region --- 11. 抽象方法 (Abstract Methods) --- - /// - /// 驱动层启动逻辑(子类必须实现) - /// 包含:设备登录、码流订阅、取流线程启动等 + /// 清空审计日志 /// - /// 取消令牌 - protected abstract Task OnStartAsync(CancellationToken token); - - /// - /// 驱动层停止逻辑(子类必须实现) - /// 包含:码流停止、设备登出、资源释放等 - /// - protected abstract Task OnStopAsync(); - - /// - /// 驱动层元数据获取逻辑(子类必须实现) - /// - /// 设备元数据 - protected abstract Task OnFetchMetadataAsync(); + public void ClearAuditLogs() + { + _auditLogs.Clear(); + AddAuditLog("用户清空了审计日志"); + } #endregion - #region --- 12. 资源清理 (Resource Disposal) --- + #region --- 14. 资源释放 (Disposal) --- /// /// 同步销毁入口(死锁免疫) @@ -661,8 +735,6 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC GC.SuppressFinalize(this); } - private volatile bool _isDisposed = false; - /// /// 异步销毁资源(优雅关闭) /// @@ -714,65 +786,4 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC } #endregion - - #region --- 13. 内部字段与补全属性 --- - - /// 跟踪上一个未完成的生命周期任务 - private Task _pendingLifecycleTask = Task.CompletedTask; - - /// 帧控制器(用于帧分发策略管理) - public FrameController Controller { get; protected set; } - - #endregion - - #region --- 14. 自愈辅助字段 (Resilience Helpers) --- - - /// - /// 认证类致命错误标记(如密码错、用户锁定) - /// 作用:触发 15 分钟长冷冻期,防止 IP 被相机锁定 - /// - public bool IsAuthFailed { get; set; } - - /// - /// 上次尝试执行 StartAsync 的系统 Tick 时间 (单调时钟) - /// - private long _lastStartAttemptTick = 0; - public long LastStartAttemptTick => Interlocked.Read(ref _lastStartAttemptTick); - - /// - /// 更新最后一次启动尝试的时间戳为当前时间 - /// - public void MarkStartAttempt() - { - Interlocked.Exchange(ref _lastStartAttemptTick, Environment.TickCount64); - } - - /// - /// 强制重置自愈相关的冷却与错误标记 - /// 用于用户手动干预(如修改密码)后,使协调器能立即触发下一次尝试 - /// - public void ResetResilience() - { - // 1. 清除认证失败标记 - IsAuthFailed = false; - - // 2. 将尝试时间戳归零 - // 这样在 Coordinator 中计算 elapsed = now - 0,结果会远大于 30s - Interlocked.Exchange(ref _lastStartAttemptTick, 0); - - _sdkLog.Debug($"[Sdk] 设备 {Id} 自愈状态已人工重置"); - } - - #endregion - - // 自动从 SmartFrame 中提取 - public int Width { get; protected set; } - public int Height { get; protected set; } - - public void ClearAuditLogs() - { - _auditLogs.Clear(); - AddAuditLog("用户清空了审计日志"); - } - } \ No newline at end of file diff --git a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs index 89ac54b..7ff3b1a 100644 --- a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs +++ b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs @@ -8,85 +8,69 @@ using System.Security; namespace SHH.CameraSdk; /// -/// [海康驱动] 工业级视频源实现 V3.4.0 (运维增强版) -/// 修复记录: -/// 1. [Fix Bug Z] 控制器遮蔽:移除子类 Controller 定义,复用基类实例,修复 FPS 控制失效问题。 -/// 2. [Feat A] 热更新支持:实现 OnApplyOptions,支持码流/句柄不亦断线热切换。 -/// 3. [Feat B] 审计集成:全面接入 AddAuditLog,对接 Web 运维仪表盘。 +/// [海康驱动] 工业级视频源实现 V3.5.0 (运维增强版) +/// 技术支撑:基于 Hikvision CH-NetSDK V6.1.x 开发 +/// 核心职责:深度封装海康私有协议,实现从原始私有码流到 BGR 零拷贝帧池的高性能转换与分发 +/// 关键修复与增强记录: +/// ✅ 1. [Fix Bug Z] 架构修正:移除子类冗余的 Controller 定义,强制复用基类 ,彻底修复多路并发下的 FPS 流控失效问题 +/// ✅ 2. [Feat A] 热更新支持:重写 ,实现码流类型(Main/Sub)与渲染句柄的动态热切换,无需重启设备链路即可生效 +/// ✅ 3. [Feat B] 运维集成:全链路接入 ,将登录、取流、重连及 SDK 报警实时推送至 Web 运维仪表盘 +/// ✅ 4. [Feat C] 性能优化:在解码回调中使用 竞争锁,有效规避在设备断开瞬间可能产生的驱动层死锁 /// public class HikVideoSource : BaseVideoSource, IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature { - #region --- 静态资源 (Global Resources) --- + #region --- 1. 静态资源与全局路由 (Static Resources) --- - // 日志实例 + /// 海康 SDK 专用日志实例 protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk); - // 静态路由表 + /// 全局句柄映射表:用于静态异常回调分发至具体实例 private static readonly ConcurrentDictionary _instances = new(); - // 全局异常回调 + + /// 静态异常回调委托引用 private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException; - // 端口抢占锁 + + /// 全局播放端口抢占锁 private static readonly object _globalPortLock = new(); #endregion - // 声明组件 + #region --- 2. 实例成员与组件引用 (Instance Members) --- + + // 协议功能组件 private readonly HikTimeSyncProvider _timeProvider; private readonly HikRebootProvider _rebootProvider; private readonly HikPtzProvider _ptzProvider; - // ========================================== - // 实现 IHikContext (核心数据暴露) - // ========================================== - public int GetUserId() => _userId; // 暴露父类或私有的 _userId - public string GetDeviceIp() => Config.IpAddress; - - // ========================================== - // 实现 ITimeSyncFeature (路由转发) - // ========================================== - // 核心逻辑:全部委托给 _timeProvider 处理,自己不写一行逻辑 - public Task GetTimeAsync() => _timeProvider.GetTimeAsync(); - - public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time); - - public Task RebootAsync() => _rebootProvider.RebootAsync(); - - public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4) - => _ptzProvider.PtzControlAsync(action, stop, speed); - - public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4) - => _ptzProvider.PtzStepAsync(action, durationMs, speed); - - #region --- 实例成员 (Instance Members) --- - + // SDK 句柄与资源 private int _userId = -1; // SDK 登录句柄 private int _realPlayHandle = -1; // 预览句柄 private int _playPort = -1; // 播放端口 - private readonly object _initLock = new(); - private readonly object _bufferLock = new(); + // 同步控制 + private readonly object _initLock = new(); // 登录句柄初始化锁 + private readonly object _bufferLock = new(); // 帧缓冲锁 + private volatile int _connectionEpoch = 0; // 连接轮询版本号 - private volatile int _connectionEpoch = 0; - - // 回调委托引用 (防止GC) + // 回调委托(强引用防止GC回收) private HikNativeMethods.REALDATACALLBACK? _realDataCallBack; private HikPlayMethods.DECCBFUN? _decCallBack; - // 内存复用对象 + // 图像处理资源, 内存复用对象 private Mat? _sharedYuvMat; - private Mat? _sharedBgrMat; // (如有需要可复用,当前逻辑直接用FramePool) - + private Mat? _sharedBgrMat; private FramePool? _framePool; private bool _isPoolReady = false; - // 【关键修复 Bug Z】: 删除了这里原本的 "public FrameController Controller..." - // 直接使用 BaseVideoSource.Controller - #endregion - #region --- 构造函数 (Constructor) --- + #region --- 3. 构造函数 (Constructor) --- + /// + /// 海康视频源实现 + /// + /// public HikVideoSource(VideoSourceConfig config) : base(config) { // 初始化组件,将 "this" 作为上下文传进去 @@ -97,8 +81,70 @@ public class HikVideoSource : BaseVideoSource, #endregion - #region --- 核心生命周期 (Core Lifecycle) --- + #region --- 4. 接口实现:IHikContext & Features (Interface Impls) --- + /// + /// 获取登录句柄 + /// + /// + public int GetUserId() => _userId; // 暴露父类或私有的 _userId + + /// + /// 获取设备IP + /// + /// + public string GetDeviceIp() => Config.IpAddress; + + /// + /// 核心逻辑:全部委托给 _timeProvider 处理,自己不写一行逻辑 + /// + /// + public Task GetTimeAsync() => _timeProvider.GetTimeAsync(); + + /// + /// 设置设备时间 + /// + /// + /// + public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time); + + /// + /// 重启设备 + /// + /// + public Task RebootAsync() => _rebootProvider.RebootAsync(); + + /// + /// PTZ 控制 + /// + /// + /// + /// + /// + public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4) + => _ptzProvider.PtzControlAsync(action, stop, speed); + + /// + /// PTZ 步长 + /// + /// + /// + /// + /// + public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4) + => _ptzProvider.PtzStepAsync(action, durationMs, speed); + + #endregion + + #region --- 5. 生命周期重写 (Lifecycle Overrides) --- + + /// + /// 启动逻辑 + /// + /// + /// + /// + /// protected override async Task OnStartAsync(CancellationToken token) { int currentEpoch = Interlocked.Increment(ref _connectionEpoch); @@ -171,6 +217,10 @@ public class HikVideoSource : BaseVideoSource, }, token); } + /// + /// 停止逻辑 + /// + /// protected override async Task OnStopAsync() { Interlocked.Increment(ref _connectionEpoch); @@ -184,6 +234,9 @@ public class HikVideoSource : BaseVideoSource, AddAuditLog($"[SDK] Hik 设备已停止. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}"); } + /// + /// 同步清理所有 SDK 资源 + /// private void CleanupSync() { lock (_initLock) @@ -268,11 +321,20 @@ public class HikVideoSource : BaseVideoSource, } } + /// + /// 获取设备元数据 + /// + /// + protected override Task OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata()); + #endregion - #region --- [新功能] 动态参数热应用 (Hot Swap) --- + #region --- 6. 驱动逻辑:热切换重写 (OnApplyOptions) --- - // 【关键修复 Feat A】实现基类的抽象方法,处理码流切换 + /// + /// 配置更新 + /// + /// protected override void OnApplyOptions(DynamicStreamOptions options) { // 1. 码流热切换逻辑 @@ -343,8 +405,12 @@ public class HikVideoSource : BaseVideoSource, #endregion - #region --- 网络取流 (Network Streaming) --- - + #region --- 7. 驱动逻辑:取流与解码 (Streaming & Decoding) --- + + /// + /// 开始预览 + /// + /// private bool StartRealPlay() { var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO @@ -361,6 +427,14 @@ public class HikVideoSource : BaseVideoSource, return _realPlayHandle >= 0; } + /// + /// 预览数据回调 + /// + /// + /// + /// + /// + /// private void SafeOnRealDataReceived(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser) { try @@ -414,11 +488,15 @@ public class HikVideoSource : BaseVideoSource, } } - #endregion - - #region --- 解码与帧分发 (Decoding) --- - - // 必须同时加上 SecurityCritical + /// + /// 必须同时加上 SecurityCritical + /// + /// + /// + /// + /// + /// + /// [HandleProcessCorruptedStateExceptions] [SecurityCritical] private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref HikPlayMethods.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2) @@ -536,8 +614,15 @@ public class HikVideoSource : BaseVideoSource, #endregion - #region --- 异常处理 --- + #region --- 8. 全局异常静态回调 --- + /// + /// SDK 报警回调 + /// + /// + /// + /// + /// private static void StaticOnSdkException(uint dwType, int lUserID, int lHandle, IntPtr pUser) { try @@ -558,6 +643,4 @@ public class HikVideoSource : BaseVideoSource, } #endregion - - protected override Task OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata()); } \ No newline at end of file