核心代码格式化

This commit is contained in:
2026-01-17 14:03:33 +08:00
parent 8482996a94
commit e06c60968d
2 changed files with 409 additions and 315 deletions

View File

@@ -3,58 +3,31 @@
namespace SHH.CameraSdk; namespace SHH.CameraSdk;
/// <summary> /// <summary>
/// [架构基类] 工业级视频源抽象核心 (V3.3.4 严格匹配版) /// [架构基类] 工业级视频源抽象核心 (V3.5.0 严格匹配版)
/// <para>当前模块: AiVideo | 核心原则: 低耦合、高并发、零拷贝</para>
/// 核心职责: /// 核心职责:
/// <para>1. 提供线程安全的生命周期管理(启动/停止/销毁)</para> /// <para>1. 生命周期管理:基于 <see cref="SemaphoreSlim"/> 实现 Start/Stop/UpdateConfig 的线程安全串行化</para>
/// <para>2. 实现状态变更的可靠分发与异常隔离</para> /// <para>2. 状态分发系统:利用 <see cref="Channel{T}"/> (DropOldest策略) 实现高性能、非阻塞的状态变更通知</para>
/// <para>3. 支持配置热更新与动态参数应用</para> /// <para>3. 遥测统计引擎:内置原子级 FPS 计算与 Mbps 带宽监控,支持网络层与解码层双路流量计费</para>
/// <para>4. 内置 FPS/码率统计、心跳保活、审计日志能力</para> /// <para>4. 弹性自愈机制:集成 IsAuthFailed 冻结期与物理网络 (Ping) 状态同步,支持 Coordinator 级自动重连</para>
/// <para>5. 审计与追踪:内置环形审计日志 (Max:100),记录配置变更、动态参数应用及驱动层异常</para>
/// 关键修复记录: /// 关键修复记录:
/// <para>✅ [Bug A] 死锁免疫:所有 await 均添加 ConfigureAwait(false),解除 UI 线程依赖</para> /// <para>✅ [V3.3.5] 资源防御:在 DisposeAsync 中强化生命周期锁,防止销毁期间触发重入</para>
/// <para>✅ [Bug π] 管道安全Dispose 采用优雅关闭策略,确保剩余状态消息被消费</para> /// <para>✅ [V3.3.5] 自愈增强:引入 Environment.TickCount64 宽限期机制,解决启动瞬间心跳超时误判</para>
/// <para>✅ [编译修复] 补全 CloneConfig 中 Transport/VendorArguments 的深拷贝逻辑</para> /// <para>✅ [Bug A] 死锁免疫:强制所有 await 添加 ConfigureAwait(false),彻底解除对同步上下文的依赖</para>
/// <para>✅ [Bug π] 管道安全Dispose 采用 TryComplete 优雅关闭策略,确保缓冲区剩余状态消息被完全消费</para>
/// </summary> /// </summary>
public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceConnectivity public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceConnectivity
{ {
// [新增] 物理在线状态(专门给 Ping 使用) #region --- 1. (Fields & Locks) ---
private volatile bool _isPhysicalOnline;
public bool IsPhysicalOnline => _isPhysicalOnline;
/// <summary> /// <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 方法,确保线程安全 /// 注意:外部修改需通过 UpdateConfig 方法,确保线程安全
/// </summary> /// </summary>
protected VideoSourceConfig _config; protected VideoSourceConfig _config;
/// <summary> /// <summary>状态同步锁:保护 _status 字段的读写原子性,防止多线程竞争导致状态不一致</summary>
/// 状态同步锁
/// 作用:保护 _status 字段的读写原子性,防止多线程竞争导致状态不一致
/// </summary>
private readonly object _stateSyncRoot = new(); private readonly object _stateSyncRoot = new();
/// <summary> /// <summary>
@@ -64,9 +37,11 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
/// </summary> /// </summary>
private readonly SemaphoreSlim _lifecycleLock = new(1, 1); 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> /// <summary> 设备在线状态标志volatile 确保多线程可见性) </summary>
private volatile bool _isActived; private volatile bool _isActived;
@@ -74,6 +49,13 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
/// <summary> 视频源核心状态(受 _stateSyncRoot 保护) </summary> /// <summary> 视频源核心状态(受 _stateSyncRoot 保护) </summary>
private VideoSourceStatus _status = VideoSourceStatus.Disconnected; private VideoSourceStatus _status = VideoSourceStatus.Disconnected;
/// <summary> 资源销毁标记 </summary>
private volatile bool _isDisposed = false;
#endregion
#region --- 2. (Status Channel) ---
/// <summary> /// <summary>
/// 状态通知有界通道 /// 状态通知有界通道
/// 特性DropOldest 策略,消费者过载时丢弃旧状态,防止内存溢出 /// 特性DropOldest 策略,消费者过载时丢弃旧状态,防止内存溢出
@@ -87,15 +69,60 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
/// <summary> 状态分发任务引用(用于 Dispose 时优雅等待) </summary> /// <summary> 状态分发任务引用(用于 Dispose 时优雅等待) </summary>
private Task? _distributorTask; private Task? _distributorTask;
#endregion
#region --- 3. (Telemetry & Stats) ---
/// <summary> 最后一帧接收的系统 Tick单调时钟不受系统时间修改影响 </summary> /// <summary> 最后一帧接收的系统 Tick单调时钟不受系统时间修改影响 </summary>
private long _lastFrameTick = 0; 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> /// <summary> 视频帧回调事件(热路径,低延迟分发) </summary>
public event Action<object>? FrameReceived; public event Action<object>? FrameReceived;
/// <summary> 状态变更事件(对外异步暴露状态通知) </summary>
public event EventHandler<StatusChangedEventArgs>? StatusChanged;
#endregion #endregion
#region --- 3. (Public Properties) --- #region --- 6. (Properties) ---
/// <summary> 视频源唯一标识 </summary> /// <summary> 视频源唯一标识 </summary>
public long Id => _config.Id; public long Id => _config.Id;
@@ -108,10 +135,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
{ {
get get
{ {
lock (_stateSyncRoot) lock (_stateSyncRoot) return _status;
{
return _status;
}
} }
} }
@@ -121,57 +145,78 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
/// <summary> 设备在线状态 </summary> /// <summary> 设备在线状态 </summary>
public bool IsActived => _isActived; public bool IsActived => _isActived;
/// <summary> 物理在线状态Ping 结果) </summary>
public bool IsPhysicalOnline => _isPhysicalOnline;
/// <summary> 设备元数据(能力集、通道信息等) </summary> /// <summary> 设备元数据(能力集、通道信息等) </summary>
public DeviceMetadata Metadata { get; protected set; } = new(); public DeviceMetadata Metadata { get; protected set; } = new();
/// <summary> 状态变更事件(对外暴露状态通知 </summary> /// <summary> 帧控制器(用于帧分发策略管理 </summary>
public event EventHandler<StatusChangedEventArgs>? StatusChanged; public FrameController Controller { get; protected set; }
/// <summary>
/// 图像预处理配置(缩放、增量等)
/// 放置在基类中确保所有接入协议HIK/DH/RTSP均可共享处理逻辑
/// </summary>
public PreprocessConfig PreprocessSettings { get; set; }
= new PreprocessConfig();
/// <summary> 最后一帧接收的 Tick 时间戳(线程安全读取) </summary> /// <summary> 最后一帧接收的 Tick 时间戳(线程安全读取) </summary>
public long LastFrameTick => Interlocked.Read(ref _lastFrameTick); public long LastFrameTick => Interlocked.Read(ref _lastFrameTick);
#endregion /// <summary> 生命周期总帧数(线程安全读取) </summary>
public long TotalFrames => Interlocked.Read(ref _totalFramesReceived);
#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> 实时 FPS每秒更新一次 </summary> /// <summary> 实时 FPS每秒更新一次 </summary>
public double RealFps { get; private set; } = 0.0; public double RealFps { get; private set; } = 0.0;
// 提供一个最近一秒的输入帧率参考值
public int NominalInputFps => (int)Math.Round(RealFps);
/// <summary> 实时码率 (Mbps) </summary> /// <summary> 实时码率 (Mbps) </summary>
protected double _currentBitrate = 0;
public double RealBitrate => _currentBitrate; public double RealBitrate => _currentBitrate;
/// <summary> 码率计算临时字节计数器 </summary> /// <summary> 视频宽度 </summary>
private long _tempByteCounter = 0; public int Width { get; protected set; }
/// <summary> 生命周期总帧数(线程安全读取) </summary> /// <summary> 视频高度 </summary>
public long TotalFrames => Interlocked.Read(ref _totalFramesReceived); public int Height { get; protected set; }
/// <summary> 满足接口要求的 IP 地址 </summary>
string IDeviceConnectivity.IpAddress => _config.IpAddress;
/// <summary> 上次启动尝试的时间戳 </summary>
public long LastStartAttemptTick => Interlocked.Read(ref _lastStartAttemptTick);
#endregion #endregion
#region --- 5. (Audit Log System) --- #region --- 7. (Abstracts) ---
/// <summary> 审计日志列表(线程安全访问) </summary> /// <summary> 子类特定的日志记录器 </summary>
private readonly List<string> _auditLogs = new(); protected abstract ILogger _sdkLog { get; }
/// <summary> 最大日志条数(滚动清除,防止内存溢出) </summary> /// <summary>
private const int MaxAuditLogCount = 100; /// 驱动层启动逻辑(子类必须实现)
/// 包含:设备登录、码流订阅、取流线程启动等
/// </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 #endregion
#region --- 6. (Constructor) --- #region --- 8. (Constructor) ---
/// <summary> /// <summary>
/// 初始化视频源基础设施 /// 初始化视频源基础设施
@@ -204,67 +249,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
#endregion #endregion
#region --- 7. (Config Management) --- #region --- 9. (Lifecycle) ---
/// <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) ---
/// <summary> /// <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>
/// 刷新设备元数据,对比差异并更新 /// 刷新设备元数据,对比差异并更新
/// </summary> /// </summary>
@@ -422,7 +467,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
#endregion #endregion
#region --- 9. (Frame Processing & Status Management) --- #region --- 11. (Data Handling) ---
/// <summary> /// <summary>
/// 检查是否存在帧订阅者(性能优化) /// 检查是否存在帧订阅者(性能优化)
@@ -432,17 +477,10 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
protected bool HasFrameSubscribers() => FrameReceived != null; protected bool HasFrameSubscribers() => FrameReceived != null;
/// <summary> /// <summary>
/// 上报驱动层异常,触发重连自愈逻辑 /// 触发帧回调事件(热路径优化)
/// </summary> /// </summary>
/// <param name="ex">相机统一异常</param> /// <param name="frameData">帧数据(如 Mat/SmartFrame</param>
protected void ReportError(CameraException ex) protected void RaiseFrameReceived(object frameData) => FrameReceived?.Invoke(frameData);
{
if (!_isActived) return;
// 标记离线并更新状态为重连中
_isActived = false;
UpdateStatus(VideoSourceStatus.Reconnecting, $"SDK异常: {ex.Message}", ex);
}
/// <summary> /// <summary>
/// 标记数据接收(心跳保活 + 双路统计) /// 标记数据接收(心跳保活 + 双路统计)
@@ -509,11 +547,49 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
} }
} }
#endregion
#region --- 12. (Status Distributor) ---
/// <summary> /// <summary>
/// 触发帧回调事件(热路径优化) /// 更新设备状态并写入分发队列
/// </summary> /// </summary>
/// <param name="frameData">帧数据(如 Mat/SmartFrame</param> /// <param name="status">新状态</param>
protected void RaiseFrameReceived(object frameData) => FrameReceived?.Invoke(frameData); /// <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> /// <summary>
/// 后台状态分发循环(单线程消费状态队列) /// 后台状态分发循环(单线程消费状态队列)
@@ -562,33 +638,45 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
} }
/// <summary> /// <summary>
/// 更新设备状态并写入分发队列 /// 上报驱动层异常,触发重连自愈逻辑
/// </summary> /// </summary>
/// <param name="status">新状态</param> /// <param name="ex">相机统一异常</param>
/// <param name="msg">状态描述</param> protected void ReportError(CameraException ex)
/// <param name="ex">关联异常(可选)</param>
protected void UpdateStatus(VideoSourceStatus status, string msg, CameraException? ex = null)
{ {
lock (_stateSyncRoot) if (!_isActived) return;
{
// 更新内部状态
_status = status;
// 写入状态队列(满时自动丢弃旧数据) // 标记离线并更新状态为重连中
_ = _statusQueue.Writer.TryWrite(new StatusChangedEventArgs( _isActived = false;
status, UpdateStatus(VideoSourceStatus.Reconnecting, $"SDK异常: {ex.Message}", ex);
msg,
ex,
ex?.RawErrorCode)
{
NewHandle = ex?.Context.TryGetValue("NativeHandle", out var handle) == true ? (IntPtr)handle : null
});
}
} }
#endregion #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> /// <summary>
/// 添加审计日志(线程安全) /// 添加审计日志(线程安全)
@@ -621,32 +709,18 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
} }
} }
#endregion
#region --- 11. (Abstract Methods) ---
/// <summary> /// <summary>
/// 驱动层启动逻辑(子类必须实现) /// 清空审计日志
/// 包含:设备登录、码流订阅、取流线程启动等
/// </summary> /// </summary>
/// <param name="token">取消令牌</param> public void ClearAuditLogs()
protected abstract Task OnStartAsync(CancellationToken token); {
_auditLogs.Clear();
/// <summary> AddAuditLog("用户清空了审计日志");
/// 驱动层停止逻辑(子类必须实现) }
/// 包含:码流停止、设备登出、资源释放等
/// </summary>
protected abstract Task OnStopAsync();
/// <summary>
/// 驱动层元数据获取逻辑(子类必须实现)
/// </summary>
/// <returns>设备元数据</returns>
protected abstract Task<DeviceMetadata> OnFetchMetadataAsync();
#endregion #endregion
#region --- 12. (Resource Disposal) --- #region --- 14. (Disposal) ---
/// <summary> /// <summary>
/// 同步销毁入口(死锁免疫) /// 同步销毁入口(死锁免疫)
@@ -661,8 +735,6 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private volatile bool _isDisposed = false;
/// <summary> /// <summary>
/// 异步销毁资源(优雅关闭) /// 异步销毁资源(优雅关闭)
/// </summary> /// </summary>
@@ -714,65 +786,4 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
} }
#endregion #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("用户清空了审计日志");
}
} }

View File

@@ -8,85 +8,69 @@ using System.Security;
namespace SHH.CameraSdk; namespace SHH.CameraSdk;
/// <summary> /// <summary>
/// [海康驱动] 工业级视频源实现 V3.4.0 (运维增强版) /// [海康驱动] 工业级视频源实现 V3.5.0 (运维增强版)
/// 修复记录: /// <para>技术支撑:基于 Hikvision CH-NetSDK V6.1.x 开发</para>
/// 1. [Fix Bug Z] 控制器遮蔽:移除子类 Controller 定义,复用基类实例,修复 FPS 控制失效问题。 /// <para>核心职责:深度封装海康私有协议,实现从原始私有码流到 BGR 零拷贝帧池的高性能转换与分发</para>
/// 2. [Feat A] 热更新支持:实现 OnApplyOptions支持码流/句柄不亦断线热切换。 /// 关键修复与增强记录:
/// 3. [Feat B] 审计集成:全面接入 AddAuditLog对接 Web 运维仪表盘。 /// <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> /// </summary>
public class HikVideoSource : BaseVideoSource, public class HikVideoSource : BaseVideoSource,
IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature
{ {
#region --- (Global Resources) --- #region --- 1. (Static Resources) ---
// 日志实例 /// <summary> 海康 SDK 专用日志实例 </summary>
protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk); protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk);
// 静态路由表 /// <summary> 全局句柄映射表:用于静态异常回调分发至具体实例 </summary>
private static readonly ConcurrentDictionary<int, HikVideoSource> _instances = new(); private static readonly ConcurrentDictionary<int, HikVideoSource> _instances = new();
// 全局异常回调
/// <summary> 静态异常回调委托引用 </summary>
private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException; private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException;
// 端口抢占锁
/// <summary> 全局播放端口抢占锁 </summary>
private static readonly object _globalPortLock = new(); private static readonly object _globalPortLock = new();
#endregion #endregion
// 声明组件 #region --- 2. (Instance Members) ---
// 协议功能组件
private readonly HikTimeSyncProvider _timeProvider; private readonly HikTimeSyncProvider _timeProvider;
private readonly HikRebootProvider _rebootProvider; private readonly HikRebootProvider _rebootProvider;
private readonly HikPtzProvider _ptzProvider; private readonly HikPtzProvider _ptzProvider;
// ========================================== // SDK 句柄与资源
// 实现 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) ---
private int _userId = -1; // SDK 登录句柄 private int _userId = -1; // SDK 登录句柄
private int _realPlayHandle = -1; // 预览句柄 private int _realPlayHandle = -1; // 预览句柄
private int _playPort = -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 HikNativeMethods.REALDATACALLBACK? _realDataCallBack;
private HikPlayMethods.DECCBFUN? _decCallBack; private HikPlayMethods.DECCBFUN? _decCallBack;
// 内存复用对象 // 图像处理资源, 内存复用对象
private Mat? _sharedYuvMat; private Mat? _sharedYuvMat;
private Mat? _sharedBgrMat; // (如有需要可复用当前逻辑直接用FramePool) private Mat? _sharedBgrMat;
private FramePool? _framePool; private FramePool? _framePool;
private bool _isPoolReady = false; private bool _isPoolReady = false;
// 【关键修复 Bug Z】: 删除了这里原本的 "public FrameController Controller..."
// 直接使用 BaseVideoSource.Controller
#endregion #endregion
#region --- (Constructor) --- #region --- 3. (Constructor) ---
/// <summary>
/// 海康视频源实现
/// </summary>
/// <param name="config"></param>
public HikVideoSource(VideoSourceConfig config) : base(config) public HikVideoSource(VideoSourceConfig config) : base(config)
{ {
// 初始化组件,将 "this" 作为上下文传进去 // 初始化组件,将 "this" 作为上下文传进去
@@ -97,8 +81,70 @@ public class HikVideoSource : BaseVideoSource,
#endregion #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) protected override async Task OnStartAsync(CancellationToken token)
{ {
int currentEpoch = Interlocked.Increment(ref _connectionEpoch); int currentEpoch = Interlocked.Increment(ref _connectionEpoch);
@@ -171,6 +217,10 @@ public class HikVideoSource : BaseVideoSource,
}, token); }, token);
} }
/// <summary>
/// 停止逻辑
/// </summary>
/// <returns></returns>
protected override async Task OnStopAsync() protected override async Task OnStopAsync()
{ {
Interlocked.Increment(ref _connectionEpoch); 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}"); AddAuditLog($"[SDK] Hik 设备已停止. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
} }
/// <summary>
/// 同步清理所有 SDK 资源
/// </summary>
private void CleanupSync() private void CleanupSync()
{ {
lock (_initLock) lock (_initLock)
@@ -268,11 +321,20 @@ public class HikVideoSource : BaseVideoSource,
} }
} }
/// <summary>
/// 获取设备元数据
/// </summary>
/// <returns></returns>
protected override Task<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());
#endregion #endregion
#region --- [] (Hot Swap) --- #region --- 6. (OnApplyOptions) ---
// 【关键修复 Feat A】实现基类的抽象方法处理码流切换 /// <summary>
/// 配置更新
/// </summary>
/// <param name="options"></param>
protected override void OnApplyOptions(DynamicStreamOptions options) protected override void OnApplyOptions(DynamicStreamOptions options)
{ {
// 1. 码流热切换逻辑 // 1. 码流热切换逻辑
@@ -343,8 +405,12 @@ public class HikVideoSource : BaseVideoSource,
#endregion #endregion
#region --- (Network Streaming) --- #region --- 7. (Streaming & Decoding) ---
/// <summary>
/// 开始预览
/// </summary>
/// <returns></returns>
private bool StartRealPlay() private bool StartRealPlay()
{ {
var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO
@@ -361,6 +427,14 @@ public class HikVideoSource : BaseVideoSource,
return _realPlayHandle >= 0; 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) private void SafeOnRealDataReceived(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser)
{ {
try try
@@ -414,11 +488,15 @@ public class HikVideoSource : BaseVideoSource,
} }
} }
#endregion /// <summary>
/// 必须同时加上 SecurityCritical
#region --- (Decoding) --- /// </summary>
/// <param name="nPort"></param>
// 必须同时加上 SecurityCritical /// <param name="pBuf"></param>
/// <param name="nSize"></param>
/// <param name="pFrameInfo"></param>
/// <param name="nReserved1"></param>
/// <param name="nReserved2"></param>
[HandleProcessCorruptedStateExceptions] [HandleProcessCorruptedStateExceptions]
[SecurityCritical] [SecurityCritical]
private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref HikPlayMethods.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2) 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 #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) private static void StaticOnSdkException(uint dwType, int lUserID, int lHandle, IntPtr pUser)
{ {
try try
@@ -558,6 +643,4 @@ public class HikVideoSource : BaseVideoSource,
} }
#endregion #endregion
protected override Task<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());
} }