Files
Ayay/SHH.CameraSdk/Core/Memory/SmartFrame.cs
2026-01-17 13:13:17 +08:00

184 lines
6.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using OpenCvSharp;
namespace SHH.CameraSdk;
/// <summary>
/// [零延迟核心] 智能帧(内存复用+引用计数)
/// 功能:封装 OpenCV Mat 实现物理内存复用,通过引用计数管理生命周期,避免 GC 频繁回收导致的性能抖动
/// 特性:引用归零自动回池,全程无内存分配/释放开销,支撑高帧率实时流处理
/// </summary>
public class SmartFrame : IDisposable
{
public ConcurrentQueue<string> SubscriberIds { get; } = new ConcurrentQueue<string>();
#region --- (Private Resources & States) ---
/// <summary> 所属帧池:用于引用归零后自动回收复用 </summary>
private readonly FramePool _pool;
/// <summary> 引用计数器:线程安全,控制帧的生命周期 </summary>
/// <remarks> 初始值 0激活后设为 1引用归零则自动回池 </remarks>
private int _refCount = 0;
#endregion
#region --- (Public Properties) ---
/// <summary> 帧数据物理内存载体OpenCV Mat 对象) </summary>
/// <remarks> 内存由帧池预分配,全程复用,不触发 GC </remarks>
public Mat InternalMat { get; private set; }
/// <summary> [快捷属性] 原始图像宽度 (若 TargetMat 为空则返回 0) </summary>
public int InternalWidth => InternalMat?.Width ?? 0;
/// <summary> [快捷属性] 原始图像高度 (若 TargetMat 为空则返回 0) </summary>
public int InnernalHeight => InternalMat?.Height ?? 0;
/// <summary> 帧激活时间戳(记录帧被取出池的时刻) </summary>
public DateTime Timestamp { get; private set; }
#endregion
#region --- (Derivative Properties) ---
/// <summary>
/// [衍生] 目标图像副本(处理后的图像)
/// <para>用途:存储经过缩放、增强后的图像,供 UI 预览或 Web 推流直接使用</para>
/// <para>生命周期:完全跟随 SmartFrame主帧销毁时自动释放</para>
/// </summary>
public Mat? TargetMat { get; private set; }
/// <summary> 变换类型(标记该 TargetMat 是被缩小了还是放大了) </summary>
public FrameScaleType ScaleType { get; private set; } = FrameScaleType.None;
/// <summary> [快捷属性] 目标图像宽度 (若 TargetMat 为空则返回 0) </summary>
public int TargetWidth => TargetMat?.Width ?? 0;
/// <summary> [快捷属性] 目标图像高度 (若 TargetMat 为空则返回 0) </summary>
public int TargetHeight => TargetMat?.Height ?? 0;
#endregion
#region --- (Constructor & Activation) ---
/// <summary>
/// 初始化智能帧(由帧池调用,外部禁止直接实例化)
/// </summary>
/// <param name="pool">所属帧池实例</param>
/// <param name="width">帧宽度</param>
/// <param name="height">帧高度</param>
/// <param name="type">帧数据类型(如 MatType.CV_8UC3</param>
internal SmartFrame(FramePool pool, int width, int height, MatType type)
{
_pool = pool;
// 预分配物理内存:内存块在帧池生命周期内复用,避免频繁申请/释放
InternalMat = new Mat(height, width, type);
// 确保激活时清理旧的衍生数据(防止脏数据残留)
ResetDerivatives();
}
private int _isReturned = 0; // 0: 激活中, 1: 已归还池
/// <summary>
/// [生产者调用] 从帧池取出时激活帧
/// 功能:初始化引用计数,标记激活时间戳
/// </summary>
public void Activate()
{
// Optimized: [原因] 使用 Exchange 强制重置归还标记,确保该帧在逻辑上完全从池中脱离,防止归还竞态
Interlocked.Exchange(ref _isReturned, 0);
// 激活后引用计数设为 1代表生产者驱动/管道)持有该帧
_refCount = 1;
// 记录帧被取出池的时间,用于后续延迟计算
Timestamp = DateTime.Now;
}
#endregion
#region --- (Derivatives Management) ---
/// <summary>
/// 挂载处理后的目标图像
/// </summary>
/// <param name="processedMat">已处理的 Mat所有权移交给 SmartFrame</param>
/// <param name="scaleType">变换类型(缩小/放大/无)</param>
public void AttachTarget(Mat processedMat, FrameScaleType scaleType)
{
// 防御性编程:如果之前已有 TargetMat先释放防止内存泄漏
if (TargetMat != null)
{
TargetMat.Dispose();
}
TargetMat = processedMat;
ScaleType = scaleType;
}
private readonly object _subscriberLock = new(); // 建议在类头部定义此锁
/// <summary>
/// 内部清理:释放衍生数据
/// </summary>
private void ResetDerivatives()
{
if (TargetMat != null)
{
TargetMat.Dispose();
TargetMat = null;
}
ScaleType = FrameScaleType.None;
// 2. [核心逻辑] 线程安全地清空订阅者列表
// 使用 lock 确保在 Clear 的瞬间,没有其他分发线程正在执行 Add 操作
lock (_subscriberLock)
{
// 注意Clear() 只是把 Count 设为 0底层数组容量 (Capacity) 不变
// 这样下次复用时SubscriberIds 不需要重新分配内存,完美符合零拷贝初衷
SubscriberIds.Clear();
}
}
#endregion
#region --- (Reference Count Management) ---
/// <summary>
/// [消费者调用] 增加引用计数
/// 适用场景:帧需要被多模块同时持有(如同时分发到 UI 和 AI 分析)
/// </summary>
public void AddRef()
{
// 原子递增:线程安全,避免多线程竞争导致计数错误
Interlocked.Increment(ref _refCount);
}
#endregion
#region --- (Disposal & Pool Return) ---
/// <summary>
/// [消费者调用] 释放引用计数
/// 核心逻辑:引用归零后自动将帧归还至帧池,实现内存复用
/// </summary>
public void Dispose()
{
// 原子递减:线程安全,确保计数准确
if (Interlocked.Decrement(ref _refCount) <= 0)
{
// 2. 关键:原子抢占归还权。只有成功将 _isReturned 从 0 变为 1 的线程才能执行归还逻辑。
if (Interlocked.CompareExchange(ref _isReturned, 1, 0) == 0)
{
// 3. 彻底清理衍生数据TargetMat 必须释放)
ResetDerivatives();
// 4. 安全归还到池中
_pool.Return(this);
}
}
}
#endregion
}