using OpenCvSharp; using SHH.CameraSdk.HikFeatures; using System.Runtime.ExceptionServices; using System.Security; namespace SHH.CameraSdk; /// /// [海康驱动] 工业级视频源实现 V3.4.0 (运维增强版) /// 修复记录: /// 1. [Fix Bug Z] 控制器遮蔽:移除子类 Controller 定义,复用基类实例,修复 FPS 控制失效问题。 /// 2. [Feat A] 热更新支持:实现 OnApplyOptions,支持码流/句柄不亦断线热切换。 /// 3. [Feat B] 审计集成:全面接入 AddAuditLog,对接 Web 运维仪表盘。 /// public class HikVideoSource : BaseVideoSource, IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature { #region --- 静态资源 (Global Resources) --- // 静态路由表 private static readonly ConcurrentDictionary _instances = new(); // 全局异常回调 private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException; // 端口抢占锁 private static readonly object _globalPortLock = new(); #endregion // 声明组件 private readonly HikTimeSyncProvider _timeProvider; private readonly HikRebootProvider _rebootProvider; private readonly HikPtzProvider _ptzProvider; // ========================================== // 实现 IHikContext (核心数据暴露) // ========================================== public int GetUserId() => _userId; // 暴露父类或私有的 _userId public string GetDeviceIp() => Config.IpAddress; // ========================================== // 实现 ITimeSyncFeature (路由转发) // ========================================== // 核心逻辑:全部委托给 _timeProvider 处理,自己不写一行逻辑 public Task GetTimeAsync() => _timeProvider.GetTimeAsync(); public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time); public Task RebootAsync() => _rebootProvider.RebootAsync(); public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4) => _ptzProvider.PtzControlAsync(action, stop, speed); public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4) => _ptzProvider.PtzStepAsync(action, durationMs, speed); #region --- 实例成员 (Instance Members) --- private int _userId = -1; // SDK 登录句柄 private int _realPlayHandle = -1; // 预览句柄 private int _playPort = -1; // 播放端口 private readonly object _initLock = new(); private readonly object _bufferLock = new(); private volatile int _connectionEpoch = 0; // 回调委托引用 (防止GC) private HikNativeMethods.REALDATACALLBACK? _realDataCallBack; private HikPlayMethods.DECCBFUN? _decCallBack; // 内存复用对象 private Mat? _sharedYuvMat; private Mat? _sharedBgrMat; // (如有需要可复用,当前逻辑直接用FramePool) private FramePool? _framePool; private bool _isPoolReady = false; // 【关键修复 Bug Z】: 删除了这里原本的 "public FrameController Controller..." // 直接使用 BaseVideoSource.Controller #endregion #region --- 构造函数 (Constructor) --- public HikVideoSource(VideoSourceConfig config) : base(config) { // 初始化组件,将 "this" 作为上下文传进去 _timeProvider = new HikTimeSyncProvider(this); _rebootProvider = new HikRebootProvider(this); _ptzProvider = new HikPtzProvider(this); } #endregion #region --- 核心生命周期 (Core Lifecycle) --- protected override async Task OnStartAsync(CancellationToken token) { int currentEpoch = Interlocked.Increment(ref _connectionEpoch); await Task.Run(() => { if (currentEpoch != _connectionEpoch) return; if (!HikSdkManager.Initialize()) throw new CameraException(CameraErrorCode.SdkNotInitialized, "SDK初始化失败", DeviceBrand.HikVision); try { HikNativeMethods.NET_DVR_SetExceptionCallBack_V30(0, IntPtr.Zero, _globalExceptionCallback, IntPtr.Zero); // [审计] 记录登录动作 AddAuditLog($"正在执行物理登录... ({_config.IpAddress})"); var devInfo = new HikNativeMethods.NET_DEVICEINFO_V30(); int newUserId = HikNativeMethods.NET_DVR_Login_V30( _config.IpAddress, _config.Port, _config.Username, _config.Password, ref devInfo); if (currentEpoch != _connectionEpoch) { if (newUserId >= 0) HikNativeMethods.NET_DVR_Logout(newUserId); throw new OperationCanceledException("启动任务已过期"); } _userId = newUserId; if (_userId < 0) { uint err = HikNativeMethods.NET_DVR_GetLastError(); throw new CameraException(HikErrorMapper.Map(err), $"登录失败: {err}", DeviceBrand.HikVision, (int)err); } _instances.TryAdd(_userId, this); AddAuditLog($"物理登录成功 (UserID: {_userId})"); // 开启取流 if (!StartRealPlay()) { uint err = HikNativeMethods.NET_DVR_GetLastError(); throw new CameraException(HikErrorMapper.Map(err), $"预览失败: {err}", DeviceBrand.HikVision, (int)err); } AddAuditLog($"网络取流成功 (StreamType: {_config.StreamType})"); } catch (Exception ex) { AddAuditLog($"启动异常: {ex.Message}"); CleanupSync(); throw; } }, token); } protected override async Task OnStopAsync() { Interlocked.Increment(ref _connectionEpoch); AddAuditLog("正在执行停止流程..."); await Task.Run(() => CleanupSync()); AddAuditLog("设备已停止"); } private void CleanupSync() { lock (_initLock) { // 1. 停止预览 if (_realPlayHandle >= 0) { HikNativeMethods.NET_DVR_StopRealPlay(_realPlayHandle); _realPlayHandle = -1; } // 2. 停止解码 if (_playPort >= 0) { HikPlayMethods.PlayM4_Stop(_playPort); HikPlayMethods.PlayM4_CloseStream(_playPort); HikPlayMethods.PlayM4_FreePort(_playPort); _playPort = -1; } lock (_bufferLock) { _sharedYuvMat?.Dispose(); _sharedYuvMat = null; _sharedBgrMat?.Dispose(); _sharedBgrMat = null; } // 3. 注销登录 if (_userId >= 0) { _instances.TryRemove(_userId, out _); HikNativeMethods.NET_DVR_Logout(_userId); _userId = -1; } _framePool?.Dispose(); _framePool = null; _isPoolReady = false; } HikSdkManager.Uninitialize(); } #endregion #region --- [新功能] 动态参数热应用 (Hot Swap) --- // 【关键修复 Feat A】实现基类的抽象方法,处理码流切换 protected override void OnApplyOptions(DynamicStreamOptions options) { // 1. 码流热切换逻辑 if (options.StreamType.HasValue) { int targetStream = options.StreamType.Value; AddAuditLog($"收到码流切换请求: {targetStream},开始执行热切换..."); lock (_initLock) { // A. 停止预览 (Keep Login) if (_realPlayHandle >= 0) { HikNativeMethods.NET_DVR_StopRealPlay(_realPlayHandle); _realPlayHandle = -1; } // B. 清理播放库 (防止旧流数据残留) if (_playPort >= 0) { HikPlayMethods.PlayM4_Stop(_playPort); HikPlayMethods.PlayM4_CloseStream(_playPort); HikPlayMethods.PlayM4_FreePort(_playPort); _playPort = -1; } // C. 更新内部配置状态 _config.StreamType = targetStream; // D. 重新开启预览 if (StartRealPlay()) { AddAuditLog($"码流热切换成功 (当前: {(_config.StreamType == 0 ? "主" : "子")}码流)"); } else { uint err = HikNativeMethods.NET_DVR_GetLastError(); AddAuditLog($"码流切换失败: {err}"); } } } // 2. 句柄动态更新逻辑 (如有需要) if (options.RenderHandle.HasValue) { // 如果是硬解码模式,可以在这里调用 PlayM4_Play(port, newHandle) AddAuditLog($"收到新句柄绑定请求: {options.RenderHandle}"); } } #endregion #region --- 网络取流 (Network Streaming) --- private bool StartRealPlay() { var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO { hPlayWnd = (IntPtr)_config.RenderHandle, lChannel = _config.ChannelIndex, dwStreamType = (uint)_config.StreamType, bBlocked = false }; _realDataCallBack = new HikNativeMethods.REALDATACALLBACK(SafeOnRealDataReceived); _realPlayHandle = HikNativeMethods.NET_DVR_RealPlay_V40(_userId, ref previewInfo, _realDataCallBack, IntPtr.Zero); return _realPlayHandle >= 0; } private void SafeOnRealDataReceived(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser) { try { // 【关键位置】:在此处调用,统计网络层收到的每一字节数据 // 因为 dwBufSize > 0,MarkFrameReceived 内部只会累加码流,不会增加 FPS 计数 MarkFrameReceived(dwBufSize); if (_realPlayHandle == -1) return; // 处理系统头 if (dwDataType == HikNativeMethods.NET_DVR_SYSHEAD && _playPort == -1) { lock (_initLock) { if (_realPlayHandle == -1 || _playPort != -1) return; bool getPortSuccess; lock (_globalPortLock) { getPortSuccess = HikPlayMethods.PlayM4_GetPort(ref _playPort); } if (!getPortSuccess) return; HikPlayMethods.PlayM4_SetDisplayBuf(_playPort, 1); // 极速模式 HikPlayMethods.PlayM4_SetStreamOpenMode(_playPort, 0); if (!HikPlayMethods.PlayM4_OpenStream(_playPort, pBuffer, dwBufSize, 2 * 1024 * 1024)) { HikPlayMethods.PlayM4_FreePort(_playPort); _playPort = -1; return; } _decCallBack = new HikPlayMethods.DECCBFUN(SafeOnDecodingCallBack); HikPlayMethods.PlayM4_SetDecCallBackEx(_playPort, _decCallBack, IntPtr.Zero, 0); HikPlayMethods.PlayM4_Play(_playPort, IntPtr.Zero); } } // 处理流数据 else if (dwDataType == HikNativeMethods.NET_DVR_STREAMDATA && _playPort != -1) { HikPlayMethods.PlayM4_InputData(_playPort, pBuffer, dwBufSize); } } catch { } } #endregion #region --- 解码与帧分发 (Decoding) --- // 必须同时加上 SecurityCritical [HandleProcessCorruptedStateExceptions] [SecurityCritical] private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref HikPlayMethods.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2) { // [优化] 维持心跳,防止被哨兵误杀 MarkFrameReceived(0); // [新增] 捕获并更新分辨率 // 只有当分辨率发生变化时才写入,减少属性赋值开销 if (Width != pFrameInfo.nWidth || Height != pFrameInfo.nHeight) { Width = pFrameInfo.nWidth; Height = pFrameInfo.nHeight; } // 1. [核心流控] 询问基类控制器:这帧要不要? // 之前失效是因为操作的是子类被遮蔽的 Controller,现在复用基类 Controller,逻辑就通了。 // 传入真实的输入帧率作为参考基准 var decision = Controller.MakeDecision(Environment.TickCount64, (int)RealFps); // 如果没人要,直接丢弃,不进行 Mat 转换,节省 CPU if (!decision.IsCaptured) return; int width = pFrameInfo.nWidth; int height = pFrameInfo.nHeight; // 2. 初始化帧池 if (!_isPoolReady) { lock (_initLock) { if (!_isPoolReady) { _framePool?.Dispose(); _framePool = new FramePool(width, height, MatType.CV_8UC3, initialSize: 3, maxSize: 5); _isPoolReady = true; } } } if (_framePool == null) return; // 3. 转换与分发 SmartFrame smartFrame = _framePool.Get(); try { if (smartFrame == null) return; // 池满丢帧 using (var rawYuvWrapper = Mat.FromPixelData(height + height / 2, width, MatType.CV_8UC1, pBuf)) { Cv2.CvtColor(rawYuvWrapper, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_YV12); } smartFrame.SubscriberIds.AddRange(decision.TargetAppIds); // ========================================================================= // 【修正】删除这里的 GlobalStreamDispatcher.Dispatch! // 严禁在这里分发,因为这时的图是“生的”,还没经过 Pipeline 处理。 // =========================================================================GlobalStreamDispatcher.Dispatch(Id, smartFrame); // 4. [分发] 将决策结果传递给处理中心 // decision.TargetAppIds 包含了 "谁需要这一帧" 的信息 //GlobalProcessingCenter.Submit(this.Id, smartFrame, decision); GlobalPipelineRouter.Enqueue(Id, smartFrame, decision); } catch (Exception ex) { smartFrame.Dispose(); // 这里为了性能不频繁写日志,仅在调试时开启 // Debug.WriteLine(ex.Message); } finally { if (smartFrame != null) smartFrame.Dispose(); } } #endregion #region --- 异常处理 --- private static void StaticOnSdkException(uint dwType, int lUserID, int lHandle, IntPtr pUser) { try { if (_instances.TryGetValue(lUserID, out var instance)) { instance.AddAuditLog($"SDK报警异常: 0x{dwType:X}"); // 写入审计 instance.ReportError(new CameraException( CameraErrorCode.NetworkUnreachable, $"SDK全局异常: 0x{dwType:X}", DeviceBrand.HikVision)); } } catch { } } #endregion protected override Task OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata()); }