2026-01-17 15:41:55 +08:00
|
|
|
|
using Ayay.SerilogLogs;
|
|
|
|
|
|
using OpenCvSharp;
|
|
|
|
|
|
using Serilog;
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
namespace SHH.CameraSdk;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// [零延迟核心] 智能帧对象池
|
|
|
|
|
|
/// 功能:预分配并复用 SmartFrame 实例,杜绝频繁 new Mat() 与 GC 回收,消除内存分配停顿
|
|
|
|
|
|
/// 核心策略:
|
|
|
|
|
|
/// <para>1. 预热分配:启动时创建初始数量帧,避免运行时内存申请</para>
|
|
|
|
|
|
/// <para>2. 上限控制:最大池大小限制内存占用,防止内存溢出</para>
|
|
|
|
|
|
/// <para>3. 背压丢帧:池空时返回 null,强制丢帧保证实时性,不阻塞生产端</para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class FramePool : IDisposable
|
|
|
|
|
|
{
|
2026-01-17 15:41:55 +08:00
|
|
|
|
private ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk);
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
#region --- 私有资源与配置 (Private Resources & Configurations) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 可用帧队列(线程安全):存储待借出的空闲智能帧 </summary>
|
|
|
|
|
|
private readonly ConcurrentQueue<SmartFrame> _availableFrames = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 所有已分配帧列表:用于统一销毁释放内存 </summary>
|
|
|
|
|
|
private readonly List<SmartFrame> _allAllocatedFrames = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 创建新帧锁:确保多线程下创建新帧的线程安全 </summary>
|
|
|
|
|
|
private readonly object _lock = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 帧宽度(与相机输出分辨率一致) </summary>
|
|
|
|
|
|
private readonly int _width;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 帧高度(与相机输出分辨率一致) </summary>
|
|
|
|
|
|
private readonly int _height;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 帧数据类型(如 CV_8UC3 对应 RGB 彩色图像) </summary>
|
|
|
|
|
|
private readonly MatType _type;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 池最大容量:限制最大分配帧数,防止内存占用过高 </summary>
|
|
|
|
|
|
private readonly int _maxPoolSize;
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region --- 构造与预热 (Constructor & Warm-Up) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化智能帧对象池
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="width">帧宽度</param>
|
|
|
|
|
|
/// <param name="height">帧高度</param>
|
|
|
|
|
|
/// <param name="type">帧数据类型</param>
|
|
|
|
|
|
/// <param name="initialSize">初始预热帧数(默认5)</param>
|
|
|
|
|
|
/// <param name="maxSize">池最大容量(默认10)</param>
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建新智能帧并加入池(内部调用,加锁保护)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CreateNewFrame()
|
|
|
|
|
|
{
|
|
|
|
|
|
var frame = new SmartFrame(this, _width, _height, _type);
|
|
|
|
|
|
_allAllocatedFrames.Add(frame);
|
|
|
|
|
|
_availableFrames.Enqueue(frame);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region --- 帧借出与归还 (Frame Borrow & Return) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 从池借出一个智能帧(O(1) 时间复杂度)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>可用智能帧 / 池空且达上限时返回 null(触发背压丢帧)</returns>
|
2026-01-17 15:41:55 +08:00
|
|
|
|
public SmartFrame? Get()
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 1. 优先从可用队列取帧,无锁快速路径
|
|
|
|
|
|
if (_availableFrames.TryDequeue(out var frame))
|
|
|
|
|
|
{
|
|
|
|
|
|
frame.Activate();
|
2026-01-17 15:41:55 +08:00
|
|
|
|
frame.MarkBorrowed(); // 记录起始时间
|
2025-12-26 03:18:21 +08:00
|
|
|
|
return frame;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 可用队列为空,检查是否达最大容量
|
|
|
|
|
|
if (_allAllocatedFrames.Count < _maxPoolSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 加锁创建新帧,避免多线程重复创建
|
|
|
|
|
|
lock (_lock)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 双重检查:防止等待锁期间其他线程已创建新帧
|
|
|
|
|
|
if (_allAllocatedFrames.Count < _maxPoolSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
CreateNewFrame();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 递归重试取帧
|
|
|
|
|
|
return Get();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 15:41:55 +08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 3. [自愈触发] 如果走到这里,说明池子满了且所有帧都在外借中。
|
|
|
|
|
|
// 可能存在“僵尸帧”死锁。执行强制回收哨兵。
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
if (ForceRecycleZombies())
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果哨兵成功救回了至少一帧,递归重试就能拿到帧
|
|
|
|
|
|
return Get();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
// 3. 背压策略:池空且达上限,返回 null 强制丢帧,保证生产端不阻塞
|
|
|
|
|
|
// 适用场景:消费端处理过慢导致帧堆积,丢帧保实时性
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 15:41:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 哨兵巡检:强制回收占用超过 5 秒不还的僵尸帧
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// [系统内部调用] 将帧归还至池(由 SmartFrame.Dispose 自动触发)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="frame">待归还的智能帧</param>
|
|
|
|
|
|
public void Return(SmartFrame frame)
|
|
|
|
|
|
{
|
|
|
|
|
|
_availableFrames.Enqueue(frame);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region --- 资源释放 (Resource Disposal) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 释放帧池所有资源,销毁所有 Mat 内存
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 遍历所有已分配帧,释放 OpenCV Mat 底层内存
|
|
|
|
|
|
foreach (var frame in _allAllocatedFrames)
|
|
|
|
|
|
{
|
|
|
|
|
|
frame.InternalMat.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
_allAllocatedFrames.Clear();
|
|
|
|
|
|
_availableFrames.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|