using Ayay.SerilogLogs; using OpenCvSharp; using Serilog; namespace SHH.CameraSdk; /// /// [零延迟核心] 智能帧对象池 /// 功能:预分配并复用 SmartFrame 实例,杜绝频繁 new Mat() 与 GC 回收,消除内存分配停顿 /// 核心策略: /// 1. 预热分配:启动时创建初始数量帧,避免运行时内存申请 /// 2. 上限控制:最大池大小限制内存占用,防止内存溢出 /// 3. 背压丢帧:池空时返回 null,强制丢帧保证实时性,不阻塞生产端 /// public class FramePool : IDisposable { private ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk); #region --- 私有资源与配置 (Private Resources & Configurations) --- /// 可用帧队列(线程安全):存储待借出的空闲智能帧 private readonly ConcurrentQueue _availableFrames = new(); /// 所有已分配帧列表:用于统一销毁释放内存 private readonly List _allAllocatedFrames = new(); /// 创建新帧锁:确保多线程下创建新帧的线程安全 private readonly object _lock = new(); /// 帧宽度(与相机输出分辨率一致) private readonly int _width; /// 帧高度(与相机输出分辨率一致) private readonly int _height; /// 帧数据类型(如 CV_8UC3 对应 RGB 彩色图像) private readonly MatType _type; /// 池最大容量:限制最大分配帧数,防止内存占用过高 private readonly int _maxPoolSize; #endregion #region --- 构造与预热 (Constructor & Warm-Up) --- /// /// 初始化智能帧对象池 /// /// 帧宽度 /// 帧高度 /// 帧数据类型 /// 初始预热帧数(默认5) /// 池最大容量(默认10) public FramePool(int width, int height, MatType type, int initialSize = 5, int maxSize = 10) { _width = width; _height = height; _type = type; _maxPoolSize = maxSize; // 预热:启动时预分配初始数量帧,避免运行时动态申请内存 for (int i = 0; i < initialSize; i++) { CreateNewFrame(); } } /// /// 创建新智能帧并加入池(内部调用,加锁保护) /// private void CreateNewFrame() { var frame = new SmartFrame(this, _width, _height, _type); _allAllocatedFrames.Add(frame); _availableFrames.Enqueue(frame); } #endregion #region --- 帧借出与归还 (Frame Borrow & Return) --- /// /// 从池借出一个智能帧(O(1) 时间复杂度) /// /// 可用智能帧 / 池空且达上限时返回 null(触发背压丢帧) public SmartFrame? Get() { // 1. 优先从可用队列取帧,无锁快速路径 if (_availableFrames.TryDequeue(out var frame)) { frame.Activate(); frame.MarkBorrowed(); // 记录起始时间 return frame; } // 2. 可用队列为空,检查是否达最大容量 if (_allAllocatedFrames.Count < _maxPoolSize) { // 加锁创建新帧,避免多线程重复创建 lock (_lock) { // 双重检查:防止等待锁期间其他线程已创建新帧 if (_allAllocatedFrames.Count < _maxPoolSize) { CreateNewFrame(); } } // 递归重试取帧 return Get(); } // ============================================================ // 3. [自愈触发] 如果走到这里,说明池子满了且所有帧都在外借中。 // 可能存在“僵尸帧”死锁。执行强制回收哨兵。 // ============================================================ if (ForceRecycleZombies()) { // 如果哨兵成功救回了至少一帧,递归重试就能拿到帧 return Get(); } // 3. 背压策略:池空且达上限,返回 null 强制丢帧,保证生产端不阻塞 // 适用场景:消费端处理过慢导致帧堆积,丢帧保实时性 return null; } /// /// 哨兵巡检:强制回收占用超过 5 秒不还的僵尸帧 /// private bool ForceRecycleZombies() { bool anyRescued = false; long now = Environment.TickCount64; // Optimized: [原因] 使用 lock 或 ToArray() 防止在哨兵巡检期间, // 其他线程触发 CreateNewFrame 导致 List 集合修改异常。 SmartFrame[] snapshot; lock (_lock) { snapshot = _allAllocatedFrames.ToArray(); } // 遍历所有已分配的帧,找出超时的僵尸 foreach (var frame in snapshot) { // 条件:当前不在池中(_refCount > 0)且借出时间超过 2000ms // 注意:这里需要给 SmartFrame 暴露一个只读的 RefCount 属性,或者直接判断 BorrowedTick if ((now - frame.BorrowedTick) > 2000) { // 强行重置该帧的所有权 frame.ForceReset(); // 重新塞回队列 _availableFrames.Enqueue(frame); anyRescued = true; // 记录一条警告日志,告诉你哪路视频出问题了 // 可以在 AddAuditLog 里看到 _sdkLog.Warning("[Sdk] SmartFrame(借出超2秒) 被强制回收."); } } return anyRescued; } /// /// [系统内部调用] 将帧归还至池(由 SmartFrame.Dispose 自动触发) /// /// 待归还的智能帧 public void Return(SmartFrame frame) { _availableFrames.Enqueue(frame); } #endregion #region --- 资源释放 (Resource Disposal) --- /// /// 释放帧池所有资源,销毁所有 Mat 内存 /// public void Dispose() { // 遍历所有已分配帧,释放 OpenCV Mat 底层内存 foreach (var frame in _allAllocatedFrames) { frame.InternalMat.Dispose(); } _allAllocatedFrames.Clear(); _availableFrames.Clear(); } #endregion }