Files
Ayay/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs

387 lines
13 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>
/// [海康驱动] 工业级视频源实现 V3.4.0 (运维增强版)
/// 修复记录:
/// 1. [Fix Bug Z] 控制器遮蔽:移除子类 Controller 定义,复用基类实例,修复 FPS 控制失效问题。
/// 2. [Feat A] 热更新支持:实现 OnApplyOptions支持码流/句柄不亦断线热切换。
/// 3. [Feat B] 审计集成:全面接入 AddAuditLog对接 Web 运维仪表盘。
/// </summary>
public class HikVideoSource : BaseVideoSource
{
#region --- (Global Resources) ---
// 静态路由表
private static readonly ConcurrentDictionary<int, HikVideoSource> _instances = new();
// 全局异常回调
private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException;
// 端口抢占锁
private static readonly object _globalPortLock = new();
#endregion
#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)
{
// 构造函数保持简洁
}
#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.Zero,
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 > 0MarkFrameReceived 内部只会累加码流,不会增加 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) ---
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);
}
// 4. [分发] 将决策结果传递给处理中心
// decision.TargetAppIds 包含了 "谁需要这一帧" 的信息
GlobalProcessingCenter.Submit(this.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<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());
}