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