using System.Threading.Channels; namespace SHH.CameraSdk; /// /// [架构基类] 工业级视频源抽象核心 (V3.3.4 严格匹配版) /// 核心职责:提供线程安全的生命周期管理、状态分发、配置热更新及资源清理能力。 /// 修复记录: /// 1. [Bug A] 死锁免疫:所有 await 增加 ConfigureAwait(false),解除对 UI 线程同步上下文的依赖。 /// 2. [Bug π] 管道安全:Dispose 时采用优雅关闭策略,确保最后的状态变更通知能发送出去。 /// 3. [编译修复] 补全了 CloneConfig 中对于 Transport 和 VendorArguments 的属性复制。 /// public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable { #region --- 核心配置与锁机制 (Core Config & Locks) --- // [Fix Bug δ] 核心配置对象 // 去除 readonly 修饰符以支持热更新 (Hot Update),允许在运行时替换配置实例 protected VideoSourceConfig _config; /// /// 状态同步锁 /// 作用:保护 _status 字段的读写原子性,防止多线程竞争导致的状态读取不一致 /// private readonly object _stateSyncRoot = new(); /// /// 生命周期互斥锁 /// 作用:确保 StartAsync/StopAsync/UpdateConfig 等操作串行执行,防止重入导致的状态机混乱 /// private readonly SemaphoreSlim _lifecycleLock = new(1, 1); #endregion #region --- 内部状态与基础设施 (Internal States & Infrastructure) --- // 内部状态标志位 private volatile bool _isOnline; private VideoSourceStatus _status = VideoSourceStatus.Disconnected; /// /// 状态通知队列 (有界) /// 特性:采用 DropOldest 策略,当消费者处理不过来时丢弃旧状态,防止背压导致内存溢出 [Fix Bug β] /// private readonly Channel _statusQueue; // 状态分发器的取消令牌源 private CancellationTokenSource? _distributorCts; // [新增修复 Bug π] 分发任务引用 // 作用:用于在 DisposeAsync 时执行 Task.WhenAny 等待,确保剩余消息被消费 private Task? _distributorTask; // [Fix Bug V] 单调时钟 // 作用:记录最后一次收到帧的系统 Tick,用于心跳检测,不受系统时间修改影响 private long _lastFrameTick = 0; /// 获取最后帧的时间戳 (线程安全读取) public long LastFrameTick => Interlocked.Read(ref _lastFrameTick); /// 视频帧回调事件 (热路径) public event Action? FrameReceived; #endregion #region --- 公开属性 (Public Properties) --- public long Id => _config.Id; public VideoSourceConfig Config => _config; public VideoSourceStatus Status { get { lock (_stateSyncRoot) return _status; } } public bool IsRunning { get; set; } public bool IsOnline => _isOnline; public DeviceMetadata Metadata { get; protected set; } = new(); public event EventHandler? StatusChanged; #endregion #region --- 遥测统计属性 (Telemetry Properties) --- // [新增] 遥测统计专用字段 private long _totalFramesReceived = 0; // 生命周期内总帧数 private int _tempFrameCounter = 0; // 用于计算FPS的临时计数器 private long _lastFpsCalcTick = 0; // 上次计算FPS的时间点 private double _currentFps = 0.0; // 当前实时FPS // [新增] 公开的遥测属性 (线程安全读取) public double RealFps => _currentFps; public long TotalFrames => Interlocked.Read(ref _totalFramesReceived); #endregion #region --- 构造函数 (Constructor) --- /// /// 构造函数:初始化基础设施 /// /// 视频源基础配置(含设备连接信息、通道号等) /// 配置为空时抛出 protected BaseVideoSource(VideoSourceConfig config) { if (config == null) throw new ArgumentNullException(nameof(config)); // [Fix Bug U] 初始配置深拷贝 // 防止外部引用修改导致内部状态不可控(配置防漂移) _config = CloneConfig(config); // [Fix Bug β] 初始化有界通道 // 容量 100,单读者多写者模式 _statusQueue = Channel.CreateBounded(new BoundedChannelOptions(100) { FullMode = BoundedChannelFullMode.DropOldest, SingleReader = true, SingleWriter = false }); _distributorCts = new CancellationTokenSource(); // [关键逻辑] 启动后台状态分发循环 // 明确持有 Task 引用,以便后续进行优雅关闭等待 _distributorTask = Task.Run(() => StatusDistributorLoopAsync(_distributorCts.Token)); } #endregion #region --- 配置管理 (Config Management) --- /// /// [修复 Bug δ] 更新配置实现 /// 允许在不销毁实例的情况下更新 IP、端口等参数,新配置下次连接生效 /// /// 新的视频源配置 public void UpdateConfig(VideoSourceConfig newConfig) { if (newConfig == null) return; // 1. 获取生命周期锁 // 虽然只是内存操作,但为了防止与 Start/Stop 并发导致读取到脏配置,仍需加锁 _lifecycleLock.Wait(); try { // 2. 执行深拷贝 _config = CloneConfig(newConfig); Debug.WriteLine($"[ConfigUpdated] 设备 {Id} 配置已更新 ({_config.IpAddress}),下次连接生效。"); } finally { _lifecycleLock.Release(); } } /// /// 配置深拷贝辅助方法 /// [编译修复] 严格匹配源文件中的属性复制逻辑,确保 Dictionary 等引用类型被重新创建 /// /// 源配置对象 /// 深拷贝后的配置实例 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, // 必须深拷贝字典,防止外部修改影响内部 VendorArguments = source.VendorArguments != null ? new Dictionary(source.VendorArguments) : new Dictionary() }; } #endregion #region --- 生命周期控制 (Lifecycle Control) --- /// /// 异步启动设备连接 /// 包含:状态校验、生命周期锁、非托管初始化、元数据刷新 /// public async Task StartAsync() { // [修复 Bug A] 必须加 ConfigureAwait(false) // 确保后续代码在线程池线程执行,防止 UI 线程死锁 await _lifecycleLock.WaitAsync().ConfigureAwait(false); try { // 1. 强制等待上一个生命周期动作完全结束 // 防止快速点击 Start/Stop 导致的逻辑重叠 await _pendingLifecycleTask.ConfigureAwait(false); // 2. 状态幂等性检查 if (_isOnline) return; // 3. 更新状态为连接中 UpdateStatus(VideoSourceStatus.Connecting, $"正在启动 {_config.Brand}..."); // 4. 执行具体的驱动启动逻辑 (带超时控制) using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); await OnStartAsync(cts.Token).ConfigureAwait(false); // 5. 标记运行状态 _isOnline = true; IsRunning = true; // [Fix Bug D/J] 重置心跳 // 给予初始宽限期,防止刚启动就被判定为僵尸流 Interlocked.Exchange(ref _lastFrameTick, Environment.TickCount64 + 2000); // 6. 更新状态为播放中并刷新元数据 UpdateStatus(VideoSourceStatus.Playing, "流传输运行中"); await RefreshMetadataAsync().ConfigureAwait(false); } catch (Exception ex) { // 7. 异常处理:回滚状态 _isOnline = false; UpdateStatus(VideoSourceStatus.Disconnected, $"启动失败: {ex.Message}"); throw; } finally { _lifecycleLock.Release(); } } /// /// 异步停止设备连接 /// 流程:标记离线→执行驱动停止逻辑→更新状态 /// public async Task StopAsync() { // [修复 Bug A] ConfigureAwait(false) 护体 await _lifecycleLock.WaitAsync().ConfigureAwait(false); try { // 1. 标记离线,阻断后续的数据处理 _isOnline = false; // 2. 执行具体的驱动停止逻辑 await OnStopAsync().ConfigureAwait(false); } finally { // 3. 更新状态并释放锁 UpdateStatus(VideoSourceStatus.Disconnected, "连接已断开"); _lifecycleLock.Release(); } } /// /// 刷新设备元数据(能力集) /// 对比新旧元数据差异,更新设备支持的功能、通道信息等 /// /// 元数据差异描述符 public async Task RefreshMetadataAsync() { if (!_isOnline) return MetadataDiff.None; try { // 1. 调用驱动层获取最新元数据 var latestMetadata = await OnFetchMetadataAsync().ConfigureAwait(false); // 2. 比对差异并更新 if (latestMetadata != null && latestMetadata.ChannelCount > 0) { var diff = Metadata.CompareWith(latestMetadata); Metadata = latestMetadata; Metadata.MarkSynced(); // 标记同步时间 return diff; } } catch (Exception ex) { Console.WriteLine($"[MetadataWarning] {Id}: {ex.Message}"); } return MetadataDiff.None; } /// /// 应用动态参数(如码流切换、OSD设置) /// 支持运行时调整画面分辨率、帧率、渲染句柄等 /// /// 动态配置项 public void ApplyOptions(DynamicStreamOptions options) { if (options == null || !_isOnline) return; try { // 1. 校验参数合法性 if (Metadata.ValidateOptions(options, out string error)) { // 2. 调用驱动层应用参数 OnApplyOptions(options); UpdateStatus(_status, "动态参数已应用"); } else { Debug.WriteLine($"[OptionRejected] {error}"); } } catch (Exception ex) { Debug.WriteLine($"[ApplyOptionsError] {ex.Message}"); } } // 虚方法:供子类重写具体的参数应用逻辑 protected virtual void OnApplyOptions(DynamicStreamOptions options) { } #endregion #region --- 帧处理辅助 (Frame Processing Helpers) --- /// /// 检查是否有帧订阅者 /// 用于优化性能:无订阅时可跳过解码等耗时操作 /// /// 有订阅者返回 true,否则返回 false protected bool HasFrameSubscribers() => FrameReceived != null; /// /// 上报驱动层异常 /// 将底层异常转换为 Reconnecting 状态,触发协调器介入自愈 /// /// 相机统一异常对象 protected void ReportError(CameraException ex) { if (!_isOnline) return; _isOnline = false; UpdateStatus(VideoSourceStatus.Reconnecting, $"SDK报错: {ex.Message}"); } /// /// 标记收到一帧数据(心跳保活 + FPS计算) /// [修改] 增强了 FPS 计算逻辑,每1秒结算一次实时帧率 /// protected void MarkFrameReceived() { long now = Environment.TickCount64; // 1. 更新心跳时间 (原有逻辑) Interlocked.Exchange(ref _lastFrameTick, now); // 2. 增加总帧数 (原子操作) Interlocked.Increment(ref _totalFramesReceived); // 3. 计算实时帧率 (FPS) // 注意:这里不需要加锁,因为通常回调是单线程串行的 // 即便有多线程微小竞争,对于FPS统计来说误差可忽略,优先保证性能 _tempFrameCounter++; long timeDiff = now - _lastFpsCalcTick; // 每 1000ms (1秒) 结算一次 FPS if (timeDiff >= 1000) { if (_lastFpsCalcTick > 0) // 忽略第一次冷启动的数据 { // 计算公式: 帧数 / (时间间隔秒) _currentFps = Math.Round(_tempFrameCounter / (timeDiff / 1000.0), 1); } _lastFpsCalcTick = now; _tempFrameCounter = 0; } } /// /// 触发帧回调事件 /// 向所有订阅者分发帧数据(热路径,尽量减少耗时操作) /// /// 帧数据(通常为 OpenCvSharp.Mat 或 SmartFrame) protected void RaiseFrameReceived(object frameData) => FrameReceived?.Invoke(frameData); #endregion #region --- 状态分发 (Status Distribution) --- /// /// 后台状态分发循环 /// 负责将 Channel 中的状态变更事件调度到 StatusChanged 事件订阅者 /// /// 取消令牌,用于终止分发循环 private async Task StatusDistributorLoopAsync(CancellationToken token) { try { // [修复 Bug π] 关键修复点 // 使用 CancellationToken.None 作为 WaitToReadAsync 的参数 // 含义:即使 token 被取消,只要 Channel 里还有数据,就继续读取,直到 Channel 被 Complete 且为空 while (await _statusQueue.Reader.WaitToReadAsync(CancellationToken.None).ConfigureAwait(false)) { while (_statusQueue.Reader.TryRead(out var args)) { // [Fix Bug M] 玻璃心防护:捕获用户层回调的异常,防止崩溃 try { StatusChanged?.Invoke(this, args); } catch (Exception ex) { Debug.WriteLine($"[UIEventError] {Id}: {ex.Message}"); } // 退出条件:仅当明确取消 且 队列已空 时才退出 if (token.IsCancellationRequested && _statusQueue.Reader.Count == 0) return; } // 双重检查退出条件 if (token.IsCancellationRequested && _statusQueue.Reader.Count == 0) return; } } catch (Exception ex) { Debug.WriteLine($"[DistributorFatal] {Id}: {ex.Message}"); } } /// /// 更新设备状态并写入通道 /// 线程安全,采用 DropOldest 策略防止状态队列溢出 /// /// 新状态 /// 状态描述信息 /// 可选:状态变更关联的异常 protected void UpdateStatus(VideoSourceStatus status, string msg, CameraException? ex = null) { lock (_stateSyncRoot) { _status = status; // 尝试写入有界通道,如果满了则丢弃旧数据(DropOldest策略在构造时指定) _statusQueue.Writer.TryWrite(new StatusChangedEventArgs(status, msg, ex, ex?.RawErrorCode)); } } #endregion #region --- 抽象方法 (Abstract Methods) --- /// /// 驱动层启动逻辑(必须由具体驱动实现) /// 包含设备连接、登录、取流等底层操作 /// /// 取消令牌 protected abstract Task OnStartAsync(CancellationToken token); /// /// 驱动层停止逻辑(必须由具体驱动实现) /// 包含设备登出、连接断开、资源释放等底层操作 /// protected abstract Task OnStopAsync(); /// /// 驱动层元数据获取逻辑(必须由具体驱动实现) /// 用于获取设备型号、通道能力、固件版本等信息 /// /// 设备元数据实例 protected abstract Task OnFetchMetadataAsync(); #endregion #region --- 资源清理 (Disposal) --- /// /// [Fix Bug A: 死锁终结者] 同步销毁入口 /// 原理:强制启动一个新的后台 Task 执行 DisposeAsync,并同步阻塞等待其完成 /// 效果:彻底规避了在 UI 线程直接 wait 导致的死锁问题 /// public void Dispose() { Task.Run(async () => await DisposeAsync().ConfigureAwait(false)).GetAwaiter().GetResult(); } /// /// 异步销毁资源 /// 包含:停止业务、关闭管道、断开事件引用、释放非托管资源 /// public virtual async ValueTask DisposeAsync() { // 1. 停止业务逻辑 await StopAsync().ConfigureAwait(false); // 2. [Fix Bug π] 优雅关闭状态管道 _statusQueue.Writer.TryComplete(); // 标记不再接受新数据 _distributorCts?.Cancel(); // 通知消费者准备退出 if (_distributorTask != null) { // 3. 等待分发器处理完剩余消息 // 给予 500ms 的宽限期,防止无限等待 await Task.WhenAny(_distributorTask, Task.Delay(500)).ConfigureAwait(false); } // 4. [Fix Bug ε] 强力切断事件引用 // 防止 UI 控件忘记取消订阅导致的内存泄漏 FrameReceived = null; StatusChanged = null; // 5. 释放基础资源 _lifecycleLock.Dispose(); _distributorCts?.Dispose(); GC.SuppressFinalize(this); } #endregion #region --- 内部字段 (Internal Fields) --- // 用于跟踪上一个未完成的生命周期任务 private Task _pendingLifecycleTask = Task.CompletedTask; #endregion }