海康摄像头取流示例初始签入
This commit is contained in:
171
SHH.CameraSdk/Core/Features/FrameConsumer.cs
Normal file
171
SHH.CameraSdk/Core/Features/FrameConsumer.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
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
|
||||
}
|
||||
92
SHH.CameraSdk/Core/Features/SnapshotCoordinator.cs
Normal file
92
SHH.CameraSdk/Core/Features/SnapshotCoordinator.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 截图协调器(单例模式)
|
||||
/// 功能:桥接 API 线程的截图请求与 SDK 线程的帧数据推送,实现异步截图功能
|
||||
/// 核心机制:基于 TaskCompletionSource 实现跨线程通信,支持超时控制与自动清理
|
||||
/// 线程安全:全量使用并发容器,无锁设计,支持多设备同时请求截图
|
||||
/// </summary>
|
||||
public class SnapshotCoordinator
|
||||
{
|
||||
#region --- 1. 单例实现 (Singleton Implementation) ---
|
||||
|
||||
/// <summary> 全局唯一实例 </summary>
|
||||
public static SnapshotCoordinator Instance { get; } = new();
|
||||
|
||||
/// <summary> 私有构造函数:禁止外部实例化,确保单例特性 </summary>
|
||||
private SnapshotCoordinator() { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 2. 私有任务池 (Private Task Pool) ---
|
||||
|
||||
/// <summary> 待处理截图请求任务池(线程安全) </summary>
|
||||
/// <remarks> Key = 设备ID,Value = 任务完成源,用于传递截图结果或超时信号 </remarks>
|
||||
private readonly ConcurrentDictionary<long, TaskCompletionSource<byte[]>> _pendingRequests = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3. API 线程交互接口 (API Thread Interface) ---
|
||||
|
||||
/// <summary>
|
||||
/// API 线程调用:申请指定设备的截图并异步等待结果
|
||||
/// </summary>
|
||||
/// <param name="deviceId">目标设备ID</param>
|
||||
/// <param name="timeoutMs">超时时间(默认 3000ms)</param>
|
||||
/// <returns>截图字节数组(JPEG/PNG 格式)/ 超时返回 null</returns>
|
||||
public async Task<byte[]?> RequestSnapshotAsync(long deviceId, int timeoutMs = 3000)
|
||||
{
|
||||
// 1. 创建任务完成源,用于接收 SDK 线程的截图数据
|
||||
var tcs = new TaskCompletionSource<byte[]>();
|
||||
// 2. 注册待处理请求到任务池
|
||||
_pendingRequests[deviceId] = tcs;
|
||||
|
||||
try
|
||||
{
|
||||
// 3. 配置超时控制:超时后自动取消任务
|
||||
using var cts = new CancellationTokenSource(timeoutMs);
|
||||
using (cts.Token.Register(() => tcs.TrySetCanceled()))
|
||||
{
|
||||
// 4. 等待 SDK 线程推送截图数据
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 超时异常:返回 null 表示截图失败
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 5. 无论成功/超时,最终从任务池移除请求,防止内存泄漏
|
||||
_pendingRequests.TryRemove(deviceId, out _);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 4. SDK 线程交互接口 (SDK Thread Interface) ---
|
||||
|
||||
/// <summary>
|
||||
/// SDK 线程调用:检查指定设备是否存在待处理的截图请求
|
||||
/// </summary>
|
||||
/// <param name="deviceId">目标设备ID</param>
|
||||
/// <returns>存在待处理请求返回 true,否则返回 false</returns>
|
||||
public bool HasRequest(long deviceId) => _pendingRequests.ContainsKey(deviceId);
|
||||
|
||||
/// <summary>
|
||||
/// SDK 线程调用:提交指定设备的截图数据,完成待处理请求
|
||||
/// </summary>
|
||||
/// <param name="deviceId">目标设备ID</param>
|
||||
/// <param name="data">截图字节数组(JPEG/PNG 格式)</param>
|
||||
public void ProvideSnapshot(long deviceId, byte[] data)
|
||||
{
|
||||
// 检查是否存在待处理请求,存在则推送数据并完成任务
|
||||
if (_pendingRequests.TryGetValue(deviceId, out var tcs))
|
||||
{
|
||||
tcs.TrySetResult(data);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user