聲明:本系列文章只提供交流與學習使用。文章中所有涉及到海康威視設備的SDK均可在海康威視官方網站下載得到。文章中所有除官方SDK意外的代碼均可隨意使用,任何涉及到海康威視公司利益的非正常使用由使用者自己負責,與本人無關。
前言:
上一篇文章《海康威視頻監控設備Web查看系統(一):概要篇》籠統的介紹了關于海康視頻中轉方案的思路,本文將一步步實現方案中的視頻中轉服務端。文中會涉及到一些.net socket處理和基礎的多線程操作。我用的是SDK版本是SDK_Win32_V4.2.8.1 。大家根據自己實際情況想在相應的SDK,頁面的說明里有詳細的設備型號列表。
分析官方SDK的Demo:
首先來看看官方SDK中的C#版本的Demo,官方Demo分為兩個版本,分別是“實時預覽示例代碼一”和“實時預覽示例代碼二”,因為有現成的C#版本,所以我們使用示例代碼一中的內容。首先關注名為CHCNetSDK的類,這個類封中裝了SDK中的所有非托管方法接口,我們需要來把這個類以及SDK中的DLL文件一起引入到我們的項目中,如果有對C#調用C++類庫不了解的朋友請自己Google一下,資料非常多,博客園里也有很多作者寫過這一類的文章,本文就不就這個內容做深入討論。
調用SDK沒有問題了,接下來看看SDK的使用,根據SDK使用文檔,SDK接口的調用需要通過一個標準流程,流程圖如下:

按照這個流程,我們第一步要做的是初始化SDK,然后是三個可選回調函數的設置,接著要做用戶注冊設備即設備登錄,緊接著就是核心的部分了,根據上一篇文章中講的思路,除了預覽模塊外其他幾個模塊的調用不在我們要解決的問題范疇,因此不予考慮。最后一步是注銷設備,釋放SDK資源。所以,最后根據我們的需求,流程簡化如下:

雖然標準流程如此,但是我們的服務端程序只有一個單一的任務,所以也沒有必要對為托管資源進行釋放,因為如果退出程序以后資源就會釋放,不退出程序的話,SDK資源就不應該被釋放。因此再簡化一下流程每個節點都有相應的代碼實現如如下所示:
1 //初始化SDK 2 CHCNetSDK.NET_DVR_Init(); 3 4 //用戶登錄 5 CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30(); 6 CHCNetSDK.NET_DVR_Login_V30(設備ip地址, 設備端口, 用戶名, 密碼, ref DeviceInfo); 7 //說明:關于設備IP、端口、用戶名及密碼信息請根據自己要訪問設備的設置正確填寫 8 9 //預覽模塊10 CHCNetSDK.NET_DVR_CLIENTINFO lpClientInfo = new CHCNetSDK.NET_DVR_CLIENTINFO();11 lpClientInfo.lChannel = channel;12 lpClientInfo.lLinkMode = 0x0000;13 lpClientInfo.sMultiCastIP = "";14 m_fRealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack);15 IntPtr pUser = new IntPtr();16 CHCNetSDK.NET_DVR_RealPlay_V30(m_lUserID, ref lpClientInfo, m_fRealData, pUser, 1);17 //說明:這里的NET_DVR_CLIENTINFO類中缺少預覽窗口的句柄,需要預覽時,要根據自己的項目設置NET_DVR_CLIENTINFO對象的hPlayWnd屬性
可能有朋友看到這里已經忍受不了了,說好的視頻中轉功能在哪呢?別著急,一切的處理都在回調函數RealDataCallBack中,先耐心看一下這個回調函數的簽名
void RealDataCallBack(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser)
第一個lRealHandle是預覽控件的句柄,第二個參數dwDataType說明回調接收到的數據類型,pBuffer 存放數據的緩沖區指針, dwBufSize 緩沖區大小 ,pUser 用戶數據的句柄。我做的這個視頻的中轉功能其實就是在這個回調函數中實現的。
好了,核心的代碼都摘出來了,大家按照SDK提供的Demo照貓畫虎就可以把預覽功能實現出來了。
服務端設計:
實現了預覽功能,下面看看中轉服務的實現。其中包含三個類:Server,Client以及ClientList類。
Server類主要負責從設備讀取數據并將數據緩存到服務器上,并且作為Socket監聽服務端;ClientList維護一個客戶端列表,并在Server獲取到設備數據時便利客戶端列表發送數據到客戶端;Client類主要負責將服務端緩存的數據分發到各個終端請求上。
三個類的關系及主要成員請看下圖:

Server類:

1 class Server 2 { 3 int m_lUserID = -1; 4 //頭數據 5 byte[] headStream; 6 7 ClientList clientList = ClientList.GetClientList(); 8 CHCNetSDK.REALDATACALLBACK m_fRealData; 9 Socket listenSocket; 10 Semaphore m_maxNumberAcceptedClients; 11 /// <summary> 12 /// Server構造函數,啟動服務端Socket及海康SDK獲取設備數據 13 /// </summary> 14 /// <param name="ipPoint">服務端IP配置</param> 15 /// <param name="numConnections">最大客戶端連接數</param> 16 /// <param name="channel">設備監聽通道</param> 17 public Server(IPEndPoint ipPoint, int numConnections, int channel) 18 { 19 if (!InitHK()) 20 { 21 return; 22 } 23 RunGetStream(channel); 24 25 listenSocket = new Socket(ipPoint.AddressFamily, SocketType.Stream, PRotocolType.Tcp); 26 listenSocket.Bind(ipPoint); 27 m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections); 28 listenSocket.Listen(100); 29 Console.WriteLine("開始監聽客戶端連接......"); 30 StartAccept(null); 31 } 32 33 #region HKSDK 34 35 private void RunGetStream(int channel) 36 { 37 if (m_lUserID != -1)//初始化成功 38 { 39 CHCNetSDK.NET_DVR_CLIENTINFO lpClientInfo = new CHCNetSDK.NET_DVR_CLIENTINFO(); 40 lpClientInfo.lChannel = channel; 41 lpClientInfo.lLinkMode = 0x0000; 42 lpClientInfo.sMultiCastIP = ""; 43 m_fRealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack); 44 IntPtr pUser = new IntPtr(); 45 int m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V30(m_lUserID, ref lpClientInfo, m_fRealData, pUser, 1); 46 Console.WriteLine("開始獲取視頻數據......"); 47 } 48 else//初始化 失敗,因為已經初始化了 49 { 50 Console.WriteLine("視頻數據獲取失敗......"); 51 } 52 } 53 54 private bool InitHK() 55 { 56 bool m_bInitSDK = CHCNetSDK.NET_DVR_Init(); 57 if (m_bInitSDK == false) 58 { 59 return false; 60 } 61 else 62 { 63 Console.WriteLine("設備SDK初始化成功......."); 64 CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30(); 65 m_lUserID = CHCNetSDK.NET_DVR_Login_V30("設備IP", 連接端口, "連接用戶名", "連接密碼", ref DeviceInfo); 66 if (m_lUserID != -1) 67 { 68 Console.WriteLine("監控設備登錄成功......."); 69 return true; 70 } 71 else 72 { 73 Console.WriteLine("監控設備登錄失敗,稍后再試......."); 74 return false; 75 } 76 } 77 } 78 79 private void RealDataCallBack(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser) 80 { 81 byte[] data = new byte[dwBufSize]; 82 Marshal.Copy(pBuffer, data, 0, (int)dwBufSize); 83 Console.WriteLine("監控設備連接正常......"); 84 if (dwDataType == CHCNetSDK.NET_DVR_SYSHEAD) 85 { 86 headStream = data; 87 } 88 clientList.SetSendData(data); 89 return; 90 } 91 92 #endregion 93 94 #region Socket 95 /// <summary> 96 /// 監聽客戶端 97 /// </summary> 98 /// <param name="acceptEventArg"></param> 99 private void StartAccept(SocketAsyncEventArgs acceptEventArg)100 {101 if (acceptEventArg == null)102 {103 acceptEventArg = new SocketAsyncEventArgs();104 acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);105 }106 else107 {108 acceptEventArg.AcceptSocket = null;109 }110 111 m_maxNumberAcceptedClients.WaitOne();112 bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);113 if (!willRaiseEvent)114 {115 ProcessAccept(acceptEventArg);116 }117 }118 /// <summary>119 /// 增加客戶端列表120 /// </summary>121 /// <param name="e"></param>122 private void ProcessAccept(SocketAsyncEventArgs e)123 {124 clientList.AddClient(new Client(e.AcceptSocket, headStream));125 StartAccept(e);126 }127 128 /// <summary>129 /// Socket回調函數130 /// </summary>131 /// <param name="
新聞熱點
疑難解答