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

171 lines
5.5 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>
/// [消费者] 专用渲染线程(零延迟设计)
/// 核心策略:
/// <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
}