Files
Ayay/SHH.CameraSdk/Core/Features/FrameConsumer.cs

171 lines
5.5 KiB
C#
Raw Normal View History

using OpenCvSharp;
namespace SHH.CameraSdk;
/// <summary>
/// [消费者] 专用渲染线程(零延迟设计)
/// 核心策略:
/// <para>1. 容量为1的阻塞队列仅保留最新一帧杜绝帧堆积</para>
/// <para>2. 非阻塞入队+主动丢帧:渲染慢时直接丢弃新帧,确保主线程不阻塞</para>
/// <para>3. 引用计数联动:丢帧时立即释放引用,内存自动回池复用</para>
/// </summary>
public class FrameConsumer : IDisposable
{
#region --- (Private Resources & States) ---
/// <summary> 帧缓冲队列容量1仅存储最新一帧保证零延迟渲染 </summary>
/// <remarks> BlockingCollection 封装线程安全操作GetConsumingEnumerable 支持取消令牌 </remarks>
private readonly BlockingCollection<SmartFrame> _frameBuffer = new(1);
/// <summary> 取消令牌源:用于终止渲染循环 </summary>
private readonly CancellationTokenSource _cts = new();
/// <summary> 后台渲染任务 </summary>
private Task? _renderTask;
/// <summary> OpenCV 窗口名称 </summary>
private readonly string _windowName;
#endregion
#region --- (Constructor & Lifecycle) ---
/// <summary>
/// 初始化帧渲染消费者
/// </summary>
/// <param name="windowName">OpenCV 显示窗口名称</param>
public FrameConsumer(string windowName = "Zero Latency Preview")
{
_windowName = windowName;
}
/// <summary>
/// 启动渲染线程
/// </summary>
public void Start()
{
// 防止重复启动
if (_renderTask != null) return;
// 启动长期运行的渲染任务,提升线程调度优先级
_renderTask = Task.Factory.StartNew(RenderLoop, TaskCreationOptions.LongRunning);
Console.WriteLine($"[Consumer] 渲染线程启动成功,窗口名称: {_windowName}");
}
/// <summary>
/// 停止渲染线程并清理资源
/// </summary>
public void Stop()
{
// 发送取消信号,终止渲染循环
_cts.Cancel();
// 标记队列完成添加,触发 GetConsumingEnumerable 退出遍历
_frameBuffer.CompleteAdding();
// 等待渲染任务结束最多等待1秒防止卡死
if (_renderTask != null)
{
Task.WaitAny(_renderTask, Task.Delay(1000));
_renderTask = null;
}
// 清理队列残余帧:释放所有未消费帧的引用,防止内存泄漏
while (_frameBuffer.TryTake(out var residualFrame))
{
residualFrame.Dispose();
}
Console.WriteLine($"[Consumer] 渲染线程已停止,窗口: {_windowName}");
}
#endregion
#region --- (Frame Enqueue & Render Logic) ---
/// <summary>
/// [生产端入口] 接收帧并尝试入队(非阻塞)
/// </summary>
/// <param name="frame">待渲染的智能帧</param>
public void Enqueue(SmartFrame frame)
{
// 防护:线程已停止,直接释放帧引用
if (_cts.IsCancellationRequested)
{
frame.Dispose();
return;
}
// 核心零延迟策略:非阻塞尝试入队
// 队列满 → 上一帧未渲染完成 → 丢弃当前帧,释放引用
if (!_frameBuffer.TryAdd(frame))
{
frame.Dispose();
// Debug.WriteLine($"[Drop] 渲染线程[{_windowName}]处理过慢,丢弃一帧");
}
// 入队成功 → 帧由队列托管,等待渲染线程消费
}
/// <summary>
/// 后台渲染循环(核心逻辑)
/// </summary>
private void RenderLoop()
{
// 创建 OpenCV 显示窗口
Cv2.NamedWindow(_windowName, WindowFlags.Normal);
try
{
// 阻塞遍历队列:队列空时等待,收到取消信号时退出
foreach (var frame in _frameBuffer.GetConsumingEnumerable(_cts.Token))
{
try
{
// 渲染有效性校验Mat 未释放且不为空
if (frame.InternalMat != null && !frame.InternalMat.IsDisposed)
{
// 零拷贝渲染:直接引用 InternalMat无内存复制开销
Cv2.ImShow(_windowName, frame.InternalMat);
// 1ms 等待 UI 事件响应(必须调用,否则窗口无响应)
Cv2.WaitKey(1);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[RenderError] 窗口[{_windowName}]渲染失败: {ex.Message}");
}
finally
{
// 至关重要:渲染完成后释放帧引用
// 引用计数归零 → 帧自动回池复用,避免内存泄漏
frame.Dispose();
}
}
}
catch (OperationCanceledException)
{
// 正常取消,无需处理
Debug.WriteLine($"[RenderInfo] 窗口[{_windowName}]渲染循环已取消");
}
finally
{
// 销毁 OpenCV 窗口,释放 UI 资源
Cv2.DestroyWindow(_windowName);
}
}
#endregion
#region --- (Disposal) ---
/// <summary>
/// 释放所有资源
/// </summary>
public void Dispose()
{
Stop();
_frameBuffer.Dispose();
_cts.Dispose();
}
#endregion
}