using FATrace.HKNetLib.Common; using FATrace.HKNetLib.Hardware; using FATrace.Model; using System.Reflection; using System.Runtime.InteropServices; using static FATrace.HKNetLib.Hardware.CHCNetSDK; using System.Threading; using System.Threading.Tasks; namespace FATrace.HKNetLib.Wrapper { public class HkCamera : ICamera { private bool _disposedValue; private bool _sdkInit; private int _userId; private string _serailNumber; private CameraLoginInfo _loginInfo = new CameraLoginInfo(); /// /// 初始化海康sdk /// /// public HkCamera() { _sdkInit = CHCNetSDK.NET_DVR_Init(); } static HkCamera() { //设置搜索路径,兼容x86 和 x64 var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); path = Path.Combine(path, "Lib", IntPtr.Size == 8 ? "x64" : "x86"); bool ok = SetDllDirectory(path); if (!ok) throw new System.ComponentModel.Win32Exception(); } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool SetDllDirectory(string path); /// /// 获取最新的错误信息 /// /// public string GetLastError() { var error = CHCNetSDK.NET_DVR_GetLastError(); if (error == 0) { return string.Empty; } if (Enum.TryParse(error.ToString(), out HKErrorCode result)) { return result.GetDesc(); } return $"错误码: {error}"; } public string GetSerialNumber() { return _serailNumber; } public bool Login(CameraLoginInfo cameraLoginInfo) { try { if (_userId >= 0) { Logout(); } _loginInfo = cameraLoginInfo; var loginInfo = new CHCNetSDK.NET_DVR_USER_LOGIN_INFO(); loginInfo.sDeviceAddress = cameraLoginInfo.IP; loginInfo.wPort = cameraLoginInfo.Port; loginInfo.sUserName = cameraLoginInfo.UserName; loginInfo.sPassword = cameraLoginInfo.Password; //是否异步登录:0- 否,1- 是 loginInfo.bUseAsynLogin = false; var _deviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V40(); _userId = CHCNetSDK.NET_DVR_Login_V40(ref loginInfo, ref _deviceInfo); _serailNumber = _deviceInfo.struDeviceV30.sSerialNumber; return _userId >= 0; } catch { return false; } } #region NVR信息 /// /// NVR IP /// public string NVR_IP { get; set; } /// /// NVR Port /// public ushort NVR_Port { get; set; } /// /// NVR UserName /// public string NVR_UserName { get; set; } /// /// NVR Pw /// public string NVR_Pw { get; set; } /// /// NVR VideoSavePath 保存的地址信息 /// public string NVRVideoSavePath { get; set; } #endregion #region 公共信息 /// /// 事件发布的消息 /// public event EventHandler VideoActionEventHandler; /// /// 下发视频进度过程的Handler /// public event EventHandler NVRLoadVideoProcessEventHandler; /// /// 下发视频完成的Handler /// public event EventHandler NVRLoadVideoCompleteEventHandler; /// /// 当前NVR登录信息 /// public CameraLoginInfo NVRLoginInfo { get; set; } = new CameraLoginInfo(); /// /// NVR登录状态 /// public bool NVRLoginState { get; set; } #endregion #region NVR开发 /// /// NVRCamera 集合数据 /// public List ListNVRCamera { get; set; } = new List(); /// /// NVR设备信息 DVR_DEVICEINFO_V30 /// public NET_DVR_DEVICEINFO_V30 DVRDeviceInfoV30; /// /// DVR_IPPARACFG_V40 /// public CHCNetSDK.NET_DVR_IPPARACFG_V40 DVR_IPPARACFG_V40 { get; set; } /// /// 模拟通道个数 /// private uint dwAChanTotalNum { get; set; } = 0; /// /// 数字通道个数 /// private uint dwDChanTotalNum { get; set; } = 0; public NET_DVR_GET_STREAM_UNION DVR_GET_STREAM_UNION { get; set; } public CHCNetSDK.NET_DVR_IPCHANINFO DVR_IPCHANINFO { get; set; } /// /// 最新的错误信息 /// public string LastMsgErr { get; set; } /// /// 登录的用户Id /// public Int32 m_lUserID { get; set; } = -1; /// /// 选择的通道号 /// 一般是 一个通道对应一个相机 /// public long iSelIndex = 0; /// /// 通道集合 /// [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 96, ArraySubType = UnmanagedType.U4)] public int[] iChannelNum = new int[96]; /// /// 下载的Handle /// public Int32 m_lDownHandle { get; set; } = -1; /// /// 用户登录 /// /// public bool Sdk_NET_DVR_Login_V30(string DVRIPAddress, int DVRPortNumber, string DVRUserName, string DVRPassword) { //登录设备 Login the device m_lUserID = CHCNetSDK.NET_DVR_Login_V30(DVRIPAddress, DVRPortNumber, DVRUserName, DVRPassword, ref DVRDeviceInfoV30); if (m_lUserID < 0) { LastMsgErr = GetLastError(); //MessageBox.Show(str1); NVRLoginState = false; return false; } else { //登录成功 //MessageBox.Show("Login Success!"); //btnLogin.Text = "Logout"; NVRLoginInfo.IP = DVRIPAddress; NVRLoginInfo.Port = (ushort)DVRPortNumber; NVRLoginInfo.Password = DVRPassword; NVRLoginInfo.UserName = DVRUserName; NVRLoginState = true; dwAChanTotalNum = (uint)DVRDeviceInfoV30.byChanNum; dwDChanTotalNum = (uint)DVRDeviceInfoV30.byIPChanNum + 256 * (uint)DVRDeviceInfoV30.byHighDChanNum; if (dwDChanTotalNum > 0) { InfoIPChannel(); } else { for (int i = 0; i < dwAChanTotalNum; i++) { //ListAnalogChannel(i + 1, 1); iChannelNum[i] = i + (int)DVRDeviceInfoV30.byStartChan; } // MessageBox.Show("This device has no IP channel!"); } } return true; } /// /// 获取通道信息 /// private void InfoIPChannel() { uint dwSize = (uint)Marshal.SizeOf(DVR_IPPARACFG_V40); IntPtr ptrIpParaCfgV40 = Marshal.AllocHGlobal((Int32)dwSize); Marshal.StructureToPtr(DVR_IPPARACFG_V40, ptrIpParaCfgV40, false); uint dwReturn = 0; int iGroupNo = 0; //该Demo仅获取第一组64个通道,如果设备IP通道大于64路,需要按组号0~i多次调用NET_DVR_GET_IPPARACFG_V40获取 if (!CHCNetSDK.NET_DVR_GetDVRConfig(m_lUserID, CHCNetSDK.NET_DVR_GET_IPPARACFG_V40, iGroupNo, ptrIpParaCfgV40, dwSize, ref dwReturn)) { LastMsgErr = GetLastError(); /*str1 = "NET_DVR_GET_IPPARACFG_V40 failed, error code= " + iLastErr; //*/ //获取IP资源配置信息失败,输出错误号 //MessageBox.Show(str1); } else { // succ DVR_IPPARACFG_V40 = (CHCNetSDK.NET_DVR_IPPARACFG_V40)Marshal.PtrToStructure(ptrIpParaCfgV40, typeof(CHCNetSDK.NET_DVR_IPPARACFG_V40)); for (int i = 0; i < dwAChanTotalNum; i++) { //ListAnalogChannel(i + 1, DVR_IPPARACFG_V40.byAnalogChanEnable[i]); iChannelNum[i] = i + (int)DVRDeviceInfoV30.byStartChan; } byte byStreamType; uint iDChanNum = 64; if (dwDChanTotalNum < 64) { iDChanNum = dwDChanTotalNum; //如果设备IP通道小于64路,按实际路数获取 } for (int i = 0; i < iDChanNum; i++) { iChannelNum[i + dwAChanTotalNum] = i + (int)DVR_IPPARACFG_V40.dwStartDChan; byStreamType = DVR_IPPARACFG_V40.struStreamMode[i].byGetStreamType; DVR_GET_STREAM_UNION = DVR_IPPARACFG_V40.struStreamMode[i].uGetStream; switch (byStreamType) { //目前NVR仅支持0- 直接从设备取流一种方式 case 0: dwSize = (uint)Marshal.SizeOf(DVR_GET_STREAM_UNION); IntPtr ptrChanInfo = Marshal.AllocHGlobal((Int32)dwSize); Marshal.StructureToPtr(DVR_GET_STREAM_UNION, ptrChanInfo, false); DVR_IPCHANINFO = (CHCNetSDK.NET_DVR_IPCHANINFO)Marshal.PtrToStructure(ptrChanInfo, typeof(CHCNetSDK.NET_DVR_IPCHANINFO)); ListNVRCamera.Add(new NVRCamera() { //ChNo = DVR_IPCHANINFO.byChannel, ChNo = (i + 1), IPID = DVR_IPCHANINFO.byIPID, IsOnline = DVR_IPCHANINFO.byEnable }); //列出IP通道 //ListIPChannel(i + 1, DVR_IPCHANINFO.byEnable, DVR_IPCHANINFO.byIPID); Marshal.FreeHGlobal(ptrChanInfo); break; default: break; } } } Marshal.FreeHGlobal(ptrIpParaCfgV40); } /// /// 根据时间获取DVR 视频数据信息 /// public (bool Result, string Msg) Sdk_NET_DVR_GetFileByTime_V40(DateTime startTime, DateTime endTime, string SaveVideFilePath) { if (m_lDownHandle >= 0) { return (Result: false, Msg: "正在下载,请先停止下载"); } NET_DVR_PLAYCOND struDownPara = new NET_DVR_PLAYCOND(); struDownPara.dwChannel = (uint)iChannelNum[(int)iSelIndex]; // 设置下载的开始时间 struDownPara.struStartTime.dwYear = startTime.Year; struDownPara.struStartTime.dwMonth = startTime.Month; struDownPara.struStartTime.dwDay = startTime.Day; struDownPara.struStartTime.dwHour = startTime.Hour; struDownPara.struStartTime.dwMinute = startTime.Minute; struDownPara.struStartTime.dwSecond = startTime.Second; // 设置下载的结束时间 struDownPara.struStopTime.dwYear = endTime.Year; struDownPara.struStopTime.dwMonth = endTime.Month; struDownPara.struStopTime.dwDay = endTime.Day; struDownPara.struStopTime.dwHour = endTime.Hour; struDownPara.struStopTime.dwMinute = endTime.Minute; struDownPara.struStopTime.dwSecond = endTime.Second; // 按时间下载 m_lDownHandle = CHCNetSDK.NET_DVR_GetFileByTime_V40(m_lUserID, SaveVideFilePath, ref struDownPara); if (m_lDownHandle < 0) { LastMsgErr = GetLastError(); return (Result: false, Msg: $"[NET_DVR_GetFileByTime_V40] 执行下载失败:{LastMsgErr}"); } uint iOutValue = 0; // 该接口指定了当前要下载的录像文件,调用成功后,还需要调用 NET_DVR_PlayBackControl_V40 接口的开始播放控制命令(NET_DVR_PLAYSTART)才能实现下载。 if (!CHCNetSDK.NET_DVR_PlayBackControl_V40(m_lDownHandle, CHCNetSDK.NET_DVR_PLAYSTART, IntPtr.Zero, 0, IntPtr.Zero, ref iOutValue)) { LastMsgErr = GetLastError(); try { CHCNetSDK.NET_DVR_StopGetFile(m_lDownHandle); } catch { } m_lDownHandle = -1; return (Result: false, Msg: $"[NET_DVR_PlayBackControl_V40] 执行下载控制失败:{LastMsgErr}"); } StartDownloadProgressMonitor(); return (Result: true, Msg: "下载OK!"); } #endregion #region 跟踪下载 private CancellationTokenSource _downloadCts; private const int _downloadPollIntervalMs = 500; /// /// 启动下载进度轮询,定期发布 NVRLoadVideoProcessEventHandler 事件 /// public void StartDownloadProgressMonitor() { // 先确保没有旧的轮询任务 StopDownloadProgressMonitor(); _downloadCts = new CancellationTokenSource(); var token = _downloadCts.Token; Task.Run(async () => { try { while (!token.IsCancellationRequested && m_lDownHandle >= 0) { int progress = -1; try { progress = CHCNetSDK.NET_DVR_GetDownloadPos(m_lDownHandle); } catch { progress = -1; } if (progress >= 0) { short percent = (short)Math.Min(100, progress); try { NVRLoadVideoProcessEventHandler?.Invoke(this, percent); } catch { // 事件订阅者异常不应中断轮询 } if (progress >= 100) { // 完成:停止下载,退出轮询 Sdk_NET_DVR_StopGetFile(); NVRLoadVideoCompleteEventHandler?.Invoke(this, "下载完成"); break; } } else { // 发生错误,尝试停止下载并退出 Sdk_NET_DVR_StopGetFile(); break; } try { await Task.Delay(_downloadPollIntervalMs, token); } catch { /* ignore */ } } } catch { // 轮询线程异常吞掉,避免影响上层逻辑 } }, token); } /// /// 停止下载进度轮询 /// private void StopDownloadProgressMonitor() { try { if (_downloadCts != null) { if (!_downloadCts.IsCancellationRequested) { _downloadCts.Cancel(); } _downloadCts.Dispose(); _downloadCts = null; } } catch { // 忽略取消中的异常 } } /// /// 停止当前下载并清理资源 /// /// public (bool Result, string Msg) Sdk_NET_DVR_StopGetFile() { try { // 停止轮询 StopDownloadProgressMonitor(); if (m_lDownHandle >= 0) { bool stopOk = CHCNetSDK.NET_DVR_StopGetFile(m_lDownHandle); m_lDownHandle = -1; if (!stopOk) { LastMsgErr = GetLastError(); return (false, $"[NET_DVR_StopGetFile] 停止下载失败:{LastMsgErr}"); } } return (true, "停止下载成功"); } catch (Exception) { return (false, "停止下载异常"); } } #endregion public bool IsOnline() { try { return CHCNetSDK.NET_DVR_RemoteControl(_userId, CHCNetSDK.NET_DVR_CHECK_USER_STATUS, IntPtr.Zero, 0); } catch { return false; } } public void Logout() { try { if (_userId >= 0) { CHCNetSDK.NET_DVR_Logout(_userId); _userId = -1; } } catch { } } private bool CheckLogin() { if (IsOnline()) return true; return Login(_loginInfo); } /// /// 控制云台,设备接收到控制命令后直接返回成功。不关心云台是否进行相应的动作 /// /// /// 取值范围[1,7] /// public bool StartPTZControl(PtzCommand cmd, Int32 speed = 4) { try { if (!CheckLogin()) return false; var result = CHCNetSDK.NET_DVR_PTZControlWithSpeed_Other(_userId, _loginInfo.ChannelNo, (uint)cmd, 0, (uint)speed); return result; } catch { return false; } } /// /// 停止云台 /// /// /// public bool StopPTZControl(PtzCommand cmd) { try { if (!CheckLogin()) return false; var result = CHCNetSDK.NET_DVR_PTZControlWithSpeed_Other(_userId, _loginInfo.ChannelNo, (uint)cmd, 1, 4); return result; } catch { return false; } } /// /// 抓图 /// /// /// public bool CapturePicture(string fileName) { if (!CheckLogin()) return false; try { FileInfo fi = new FileInfo(fileName); if (fi.Exists) { File.Delete(fi.FullName); } if (!Directory.Exists(fi.DirectoryName)) { Directory.CreateDirectory(fi.DirectoryName); } CHCNetSDK.NET_DVR_JPEGPARA lpJpegPara = new CHCNetSDK.NET_DVR_JPEGPARA { wPicQuality = 0, wPicSize = 0xff }; return CHCNetSDK.NET_DVR_CaptureJPEGPicture(_userId, _loginInfo.ChannelNo, ref lpJpegPara, fileName); } catch { return false; } } public bool SetFocusMode(FocusModeType focusModeType) { bool res = false; var destMode = focusModeType; CHCNetSDK.NET_DVR_FOCUSMODE_CFG focusmode_cfg = new CHCNetSDK.NET_DVR_FOCUSMODE_CFG(); focusmode_cfg.byRes = new byte[48]; focusmode_cfg.byRes1 = new byte[2]; int nSize = Marshal.SizeOf(focusmode_cfg); focusmode_cfg.dwSize = (uint)nSize; IntPtr ptrDeviceCfg = Marshal.AllocHGlobal(nSize); try { // 获取当前模式 Marshal.StructureToPtr(focusmode_cfg, ptrDeviceCfg, false); uint rSize = (uint)nSize; if (!CHCNetSDK.NET_DVR_GetDVRConfig(_userId, CHCNetSDK.NET_DVR_GET_FOCUSMODECFG, 1, ptrDeviceCfg, (uint)nSize, ref rSize)) { var errorStr = CHCNetSDK.NET_DVR_GetLastError(); return res; } // 如果当前模式与请求模式相同,则直接返回true CHCNetSDK.NET_DVR_FOCUSMODE_CFG getObj = (CHCNetSDK.NET_DVR_FOCUSMODE_CFG)Marshal.PtrToStructure(ptrDeviceCfg, typeof(CHCNetSDK.NET_DVR_FOCUSMODE_CFG)); var curMode = (FocusModeType)getObj.byFocusMode; if (curMode == destMode) { res = true; return res; } // 否则调用设置方法 getObj.fOpticalZoomLevel = 0; getObj.byOpticalZoom = 32; getObj.byFocusMode = (byte)destMode; IntPtr ptrDeviceCfg1 = Marshal.AllocHGlobal((int)getObj.dwSize); Marshal.StructureToPtr(getObj, ptrDeviceCfg1, false); res = CHCNetSDK.NET_DVR_SetDVRConfig(_userId, CHCNetSDK.NET_DVR_SET_FOCUSMODECFG, 1, ptrDeviceCfg1, focusmode_cfg.dwSize); if (!res) { var errorStr = CHCNetSDK.NET_DVR_GetLastError(); } } catch (Exception ex) { throw ex; } finally { Marshal.FreeHGlobal(ptrDeviceCfg); } return res; } /// /// 获取对焦距模式 /// /// public FocusModeType GetFocusMode() { FocusModeType retValue = FocusModeType.None; CHCNetSDK.NET_DVR_FOCUSMODE_CFG focusmode_cfg = new CHCNetSDK.NET_DVR_FOCUSMODE_CFG { byRes = new byte[48], byRes1 = new byte[2] }; int nSize = Marshal.SizeOf(focusmode_cfg); focusmode_cfg.dwSize = (uint)nSize; IntPtr ptrDeviceCfg = Marshal.AllocHGlobal(nSize); try { Marshal.StructureToPtr(focusmode_cfg, ptrDeviceCfg, false); uint rSize = (uint)nSize; if (CHCNetSDK.NET_DVR_GetDVRConfig(this._userId, CHCNetSDK.NET_DVR_GET_FOCUSMODECFG, 1, ptrDeviceCfg, (uint)nSize, ref rSize)) { CHCNetSDK.NET_DVR_FOCUSMODE_CFG getObj = (CHCNetSDK.NET_DVR_FOCUSMODE_CFG)Marshal.PtrToStructure(ptrDeviceCfg, typeof(CHCNetSDK.NET_DVR_FOCUSMODE_CFG)); retValue = (FocusModeType)getObj.byFocusMode; } } catch (Exception ex) { throw ex; } finally { Marshal.FreeHGlobal(ptrDeviceCfg); } return retValue; } public bool Shutdown() { try { if (!CheckLogin()) return false; return CHCNetSDK.NET_DVR_ShutDownDVR(_userId); } catch { return false; } } public bool Reboot() { try { if (!CheckLogin()) return false; return CHCNetSDK.NET_DVR_RebootDVR(_userId); } catch { return false; } } #region 释放接口 protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { // TODO: 释放托管状态(托管对象) } if (_sdkInit) { if (_userId >= 0) { Logout(); } try { CHCNetSDK.NET_DVR_Cleanup(); } catch { } } _disposedValue = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } #endregion } }