using AForge.Video.DirectShow; using OpenCvSharp; using Serilog; using System.Runtime.ExceptionServices; using System.Security; namespace SHH.CameraSdk; /// /// [USB驱动] 工业级视频源实现 (参考 DahuaVideoSource 逻辑重构版) /// 当前模块: AiVideo | 核心原则: 逻辑对标、高并发防御、零拷贝 /// public class UsbVideoSource : BaseVideoSource { protected override ILogger _sdkLog => Log.ForContext("SourceContext", "UsbSdk"); #region --- 1. 静态资源 (Static Resources) --- // USB 驱动通常为本地设备,无需像大华那样维护全局 ConcurrentDictionary 句柄映射 #endregion #region --- 2. 实例成员 (Instance Members) --- private VideoCapture? _capture; private CancellationTokenSource? _cts; private int _deviceIndex = -1; private FramePool? _framePool; private volatile bool _isPoolReady = false; private readonly object _initLock = new(); #endregion public UsbVideoSource(VideoSourceConfig config) : base(config) { } #region --- 3. 生命周期实现 (Lifecycle Overrides) --- protected override async Task OnStartAsync(CancellationToken token) { _cts = CancellationTokenSource.CreateLinkedTokenSource(token); await Task.Run(() => { lock (_initLock) { // 1. 获取配置的标识符 (存放在 RtspPath 中的那串 @device:sw...) string targetMoniker = _config.RtspPath; _deviceIndex = -1; _sdkLog.Information($"[SDK] 正在扫描 USB 设备以匹配 Moniker: {targetMoniker}"); // 2. 使用 AForge 列出所有视频采集设备 var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); if (videoDevices.Count == 0) { throw new Exception("系统中未检测到任何 USB 摄像头设备。"); } // 3. 遍历并匹配 for (int i = 0; i < videoDevices.Count; i++) { _sdkLog.Debug($"[Scan] Index: {i}, Name: {videoDevices[i].Name}, Moniker: {videoDevices[i].MonikerString}"); // 进行比对 if (videoDevices[i].MonikerString.Equals(targetMoniker, StringComparison.OrdinalIgnoreCase)) { _deviceIndex = i; // 找到了!这就是 OpenCV 需要的整数索引 _sdkLog.Information($"[Match] 成功匹配到设备: {videoDevices[i].Name}, 对应 OpenCV Index: {i}"); break; } } // 4. 容错处理:如果没找到,尝试 fallback 到 IpAddress 里的索引 if (_deviceIndex == -1) { if (int.TryParse(_config.IpAddress, out int fallbackIndex)) { _deviceIndex = fallbackIndex; _sdkLog.Warning($"[Match] 未找到匹配的 Moniker,退回到使用 IpAddress 指定的索引: {fallbackIndex}"); } else { throw new Exception($"无法找到指定的 USB 设备: {targetMoniker}"); } } // 5. 使用 DSHOW 模式打开 OpenCV _capture = new VideoCapture(_deviceIndex, VideoCaptureAPIs.DSHOW); _sdkLog.Information($"[SDK] USB 开启成功 => Index:{_deviceIndex}"); AddAuditLog($"[SDK] USB 开启成功"); // ... 后续设置和启动循环逻辑 ... _ = Task.Run(() => InternalCaptureLoop(_cts.Token), _cts.Token); } }, token); } protected override async Task OnStopAsync() { _cts?.Cancel(); await Task.Run(() => { lock (_initLock) { if (_capture != null) { _capture.Release(); _capture.Dispose(); _capture = null; } _framePool?.Dispose(); _isPoolReady = false; _sdkLog.Information($"[SDK] USB 已停止 ID:{Id}"); } }); } #endregion #region --- 4. 核心逻辑:采集与分发 (Core Logic) --- /// /// 内部采集循环:逻辑完全对标大华的 ProcessRealData + SafeOnDecodingCallBack /// private void InternalCaptureLoop(CancellationToken token) { while (!token.IsCancellationRequested) { try { if (_capture == null || !_capture.IsOpened()) break; // 1. 探测当前分辨率 int currentWidth = (int)_capture.FrameWidth; int currentHeight = (int)_capture.FrameHeight; if (currentWidth <= 0 || currentHeight <= 0) { Thread.Sleep(10); continue; } // 2. 模拟大华的“心跳”统计 MarkFrameReceived(0); // 3. 逻辑对标:SafeOnDecodingCallBack 中的“防死锁重建” if (!_isPoolReady || Width != currentWidth || Height != currentHeight) { bool lockTaken = false; try { Monitor.TryEnter(_initLock, 50, ref lockTaken); if (lockTaken) { if (!_isPoolReady || Width != currentWidth || Height != currentHeight) { _sdkLog.Information($"[Res] USB分辨率变更: {Width}x{Height} -> {currentWidth}x{currentHeight}"); _framePool?.Dispose(); Width = currentWidth; Height = currentHeight; // 重建帧池 (对标大华参数: 3, 5) _framePool = new FramePool(currentWidth, currentHeight, MatType.CV_8UC3, 3, 5); _isPoolReady = true; } } else continue; } finally { if (lockTaken) Monitor.Exit(_initLock); } } // 4. [流控决策] 复用基类控制器 // 注意:这里 decision 的类型取决于你基类的返回定义,通常为 StreamDecision var decision = Controller.MakeDecision(Environment.TickCount64, (int)RealFps); if (!decision.IsCaptured) { _capture.Grab(); // 仅抓取不解码,保持底层缓冲区最新 continue; } // 5. 执行数据处理 ExecuteFrameProcessing(decision); } catch (Exception ex) { _sdkLog.Error(ex, $"[SDK] USB 采集循环异常 ID:{Id}"); Thread.Sleep(500); // 异常规避 } } } /// /// 帧处理逻辑:对标大华的 SafeOnDecodingCallBack 内部逻辑 /// [HandleProcessCorruptedStateExceptions] [SecurityCritical] private void ExecuteFrameProcessing(dynamic decision) // 使用 dynamic 规避特定的类定义编译问题 { SmartFrame? smartFrame = null; try { smartFrame = _framePool?.Get(); if (smartFrame == null) return; // [核心操作:零拷贝思想] // 直接将 USB 原始 BGR 数据读入 SmartFrame 分配好的内存中 if (_capture != null && _capture.Read(smartFrame.InternalMat)) { // 🛡️ [空结果防御] if (smartFrame.InternalMat.Empty()) { _sdkLog.Warning($"[SDK] USB 解码后 Mat 为空. ID:{Id}"); return; } Cv2.CvtColor(smartFrame.InternalMat, smartFrame.InternalMat, ColorConversionCodes.BGR2RGB); // 填充订阅者 foreach (var appId in decision.TargetAppIds) smartFrame.SubscriberIds.Enqueue(appId); // 发送到全局路由 (Router 内部必须 AddRef) GlobalPipelineRouter.Enqueue(Id, smartFrame, decision); } } catch (Exception ex) { _sdkLog.Error(ex, $"[SDK] USB 帧转换流程异常. ID:{Config.Id}"); AddAuditLog($"[SDK] USB 处理异常: {ex.Message}"); } finally { // ♻️ [引用闭环] 对标大华的 finally Dispose smartFrame?.Dispose(); } } #endregion #region --- 5. 其它实现 --- protected override void OnApplyOptions(DynamicStreamOptions options) { } protected override Task OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata()); #endregion }