C# 視頻監控系列(8):服務器端——預覽和可被客戶端連接

前言

     在客戶端相關的文章還沒有寫出來的時候,服務器端已經差不多了,沒有很及時的把文章一篇接一篇的寫是有理由的——有些功能我項目中暫時沒有加入,只是對照API知道有這個功能,邊寫文章邊做例子,這樣一來發現有些API封裝的不對,所以把這系列的文章寫的速度都放慢了,以求儘量每一篇文章都正確。當然還是免不了找藉口說太忙,現在在寫播放器部分的代碼,進展目前看來還順利: )

 

注意

     本系列文章限於學習交流,注重過程,由於涉及公司,所以不提供源代碼下載,非常抱歉!!但是請大家放心,核心、實現以及其他能夠貼出來的代碼我都會貼出來,並且爭取盡所能的回答留言裏的每一個問題,感謝大家關注,歡迎交流 :)

 

系列

 

系列

     1.     C# 視頻監控系列(1):準備

     2.     C# 視頻監控系列(2):客戶端——封裝API

     3.     C# 視頻監控系列(3):客戶端——連接服務器

     4.     C# 視頻監控系列(4):客戶端——音頻接收和抓圖

     5.     C# 視頻監控系列(5):客戶端——給服務器端發送字符串和錄像(數據捕獲)

     6.     C# 視頻監控系列(6):服務器端——封裝API(上) [HikServer.dll]

     7.     C# 視頻監控系列(7):服務器端——封裝API(下) [DS40xxSDK.dll]

     8.     C# 視頻監控系列(8):服務器端——預覽和可被客戶端連接

 

推薦文章

     1.     如果非託管代碼需要多次調用託管代碼中的委託,請將委託保存爲成員變量。  極其重要的一篇文章,非常建議你把每一個委託都實例成成員變量,在正文中代碼可以看到,很多執行委託的地方報錯絕大部分都是這個原因!!

 

正文

     一、VC++ Demo裏關於這兩個功能的實現和分析

          基本上每段代碼都可以從OnInitDialog這個方法開始分析

          1.1.     VC++ Code:

               HikVisionDlg.cpp 的OnInitDialog方法中的關鍵代碼

複製代碼
    for(i = 0; i < GetTotalDSPs(); i++)
    {
        ChannelHandle[i] 
= ChannelOpen(i);
        
if (ChannelHandle[i]<0
        {
            AfxMessageBox(
"channel open error > 0");

        }
        
else if (ChannelHandle[i] ==(HANDLE) 0xffff)
        {
            AfxMessageBox(
"channel open error 0xffff");

        }

        gChannelTotalLength[i] 
= 0;
        nowstate[i]
=0;

        
if (servertype == DIALTYPE)
        {
            SetIBPMode(ChannelHandle[i],
211,2,1,8);
            SetDefaultQuant(ChannelHandle[i],
18,18,23);
            SetStreamType(ChannelHandle[i],STREAM_TYPE_VIDEO);
        }
        
else
        {
            SetIBPMode(ChannelHandle[i],
100,2,1,25);
            SetDefaultQuant(ChannelHandle[i],
15,15,20);
        }
    }

    
if (servertype == DIALTYPE)
    {
        
for(i = 0; i < GetTotalDSPs(); i++)
            SetEncoderPictureFormat(ChannelHandle[i], ENC_QCIF_FORMAT);

    }
    
else
    {

        
for(i = 0; i < GetTotalDSPs(); i++)
        {
            
if ( i==0 )
            {
                
//when initiated,set the first channel as 4CIF encode,others as CIF
                SetEncoderPictureFormat(ChannelHandle[0], ENC_4CIF_FORMAT);
                bEncodeCifAndQcif[
0= FALSE;
            }
            
else
            {
                SetEncoderPictureFormat(ChannelHandle[i], ENC_CIF_FORMAT);
            }

        }

    }

//    int id = IDC_CHECK2;
//    for(i = 0; i < MAX_CHANNELS; i++){
//        GetDlgItem(id + i)->EnableWindow(FALSE);
//    }
    RegisterStreamDirectReadCallback(::StreamDirectReadCallback,this);
    RegisterMessageNotifyHandle(m_hWnd, MsgDataReady);


    MP4_ServerSetMessage(WM_MYCOMMAND,
this->m_hWnd);

    gCapImages 
= 0;

    SetOverlayColorKey(gBackgroundColor);

    gTimer 
= SetTimer(110000);
    SetTimer(
2,2000,0);
    SetTimer(
5,5000,0);

    
for (i=0;i<MAX_CHANNELS;i++)
        gCurrentFileLen[i] 
= 0;

    SERVER_VIDEOINFO videoinfo;
    g_nChannelTotal 
= GetTotalDSPs();

    
for( i=0 ; i < g_nChannelTotal; i++ )
    {
        
if(i == 0)
        {
            MP4_ServerSetBufNum(i,
90);
        }
        
else
        {
            MP4_ServerSetBufNum(i,
80);
        }
        
        
if (servertype == DIALTYPE)
            videoinfo.m_datatype[i] 
= DIALING;
        
else
            videoinfo.m_datatype[i] 
= NORMAL;
    }

    videoinfo.m_datatype[
0= SMALLPIC;

    videoinfo.m_channum 
= g_nChannelTotal;
    videoinfo.m_waittime 
= 2;

    MP4_ServerSetStart(StartCap);
    MP4_ServerSetStop(StopCap);

    MP4_ServerSetIBPMode(SetIBP);
    MP4_ServerSetCapIFrame(MakeIFrame);

    MP4_ServerSetTTL(
64);
    MP4_ServerSetNetPort(
5050,6050);

    MP4_ServerCheckIP(CheckIP);
    MP4_ServerCheckPassword(checkpassword);

    
//set the max connector of  channel 0
    MP4_ServerMaxUser(0,24);

    
//如果想不使用缺省方式進行多播,
    
//可以調用下面的函數設置自己的多播信息
    
//詳細信息請參考SDK文檔
//    MP4_ServerCastGroup(TRUE,0,"228.0.0.132",9988);

    
if (!MP4_ServerStart(&videoinfo))
    {
        MessageBox(
"error","error",MB_OK);
    }
複製代碼

               HikVisionDlg.cpp 的StreamDirectReadCallback方法

複製代碼
int __cdecl StreamDirectReadCallback(ULONG channelNum,void *DataBuf,DWORD Length,int frameType,void *context)
{


    
int i,status=0;
    CString ctip;
    
int nframetype =0;

    
// if cap images we need clean the queue here
//    if (!bCapture)
//        return 0;

     
// no errors
     if(frameType > 0) {
         
if(frameType == PktSysHeader){     
             
// store the file header             
             memcpy(FileHeader[channelNum], DataBuf, Length);
             FileHeaderLen 
= Length;
             TRACE(
"channel %d get the file header !\n",channelNum);
            
         }

         
if(frameType == PktIFrames || frameType ==PktSubIFrames){
             status 
= 1;
         }
         
else{
             status 
= 0;
         }
            
         
if(frameType == PktMotionDetection){
//             m_VideoWin.DrawVect(channelNum, (char *)DataBuf, Length);
             return 0;
         }
         
if(frameType == PktOrigImage){
             
return 0;
         }


     }

     
if(Length == 0){
         TRACE(
"no data ?\n");
         
return 0;
     }

//     if(frameType == PktIFrames){
//         int iii=1;
//     }

    ULONG currentTime 
= timeGetTime();

    gChannelTotalLength[channelNum] 
+= Length;
    gCurrentFileLen[channelNum] 
+= Length;

    
if(currentTime > StartTime+1000){

        CString str,str2;
        str.Format(
"%d", (gChannelTotalLength[dcurrentwin] *8/(currentTime - StartTime)));
        
for(i=0;i<g_nChannelTotal;i++)
            gChannelTotalLength[i] 
= 0;
         StartTime
= currentTime; 
        CHKVisionDlg 
*pMain = (CHKVisionDlg *)AfxGetMainWnd();
         pMain
->GetDlgItem(IDC_BPS)->SetWindowText((LPCTSTR)str);
    }

//    if (m_sframe && channelNum ==0)
//    {
 
//          if((frameType == PktSFrames && nframetype ==4 )||(frameType == PktSysHeader))
//         {
//            MP4_ServerWriteData(channelNum,(unsigned char *)DataBuf, Length,frameType,status);    
//         }
//    }
    
//    MP4_ServerWriteData(channelNum,(unsigned char *)DataBuf, Length,frameType,status);    
    
    
if(frameType ==PktAudioFrames)
    {
        _write(gFileHandleQcif[channelNum],DataBuf,Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char *)DataBuf, Length,frameType,status,1);
        _write(gFileHandle[channelNum], DataBuf, Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char *)DataBuf, Length,frameType,status,0);
        
    }
else if (frameType ==PktSubIFrames || frameType ==PktSubPFrames || frameType == PktSubBBPFrames || frameType == PktSubSysHeader)
    {
        
        _write(gFileHandleQcif[channelNum],DataBuf,Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char *)DataBuf, Length,frameType,status,1);    
    }
else 
    {
        _write(gFileHandle[channelNum], DataBuf, Length);
        MP4_ServerWriteDataEx(channelNum,(unsigned 
char *)DataBuf, Length,frameType,status,0);
    }

    
return 0;
}
複製代碼

               VideoWin.cpp的OnPaint方法               

StartVideoPreview(&dc);

               VideoWin.cpp的StartVideoPreview方法

複製代碼
    for(int i = 0; i < GetTotalDSPs(); i++){
        StopVideoPreview(ChannelHandle[i]);
    }

    RECT previewWnd;
    GetClientRect(
&previewWnd);

    
//CDC *pDC = GetDlgItem(IDC_VIDEOWIN)->GetDC();
    CBrush tempBrush(RGB(101010));
    CBrush 
*oldBrush = dc->SelectObject(&tempBrush);
    dc
->Rectangle(&previewWnd);
    dc
->SelectObject(oldBrush);

    
int rectWidth = previewWnd.right - previewWnd.left;
    
int rectHeight = previewWnd.bottom - previewWnd.top;

    
int numRects = GetTotalDSPs();


    ZeroMemory(rectList, 
sizeof(rectList));

    numRects 
= CacRects(GetTotalDSPs());
    
    
for(i = 0; i < GetTotalDSPs(); i++){
        
if(bDdrawMode)
            ::StartVideoPreview(ChannelHandle[i], m_hWnd, 
&rectList[i], FALSE, vdfRGB16, 25);
        
else 
            ::StartVideoPreview(ChannelHandle[i], m_hWnd, 
&rectList[i], FALSE, vdfYUV422Planar, 25);
    }
複製代碼

          1.2.     代碼分析

               1.     從OnInitDialog中並參照《DS-4000HC、HCS、HC+、HF、HS、MD卡的Windows編程指南V4.3》的[API調用順序](pdf 21頁)以及對應的註釋能看得出基本上是做板卡的初始化,服務器的初始化等。

               2.     StreamDirectReadCallback回調函數主要是通過MP4_ServerWriteDataEx將數據寫入內存(文檔註釋:往發送緩存寫數據。)和用_write寫文件做存儲視頻錄像。

               3.     預覽的代碼是在OnPaint事件調用的。

     二、服務器端預覽

          C# Code:

複製代碼
        #region 變量

        IntPtr ChannelHandle;

        
#endregion

        
#region 窗體事件

        
private void Form2_Load(object sender, EventArgs e)
        {
            
//設置系統默認的視頻制式
            HikVisionSDK.SetDefaultVideoStandard(VideoStandard_t.StandardNTSC);

            
//初始化板卡
            if (HikVisionSDK.InitDSPs() < 0)
            {
                MessageBox.Show(
"初始化DSPs失敗!!");
                
return;
            }

            
if (HikVisionSDK.GetTotalDSPs() == 0)
            {
                MessageBox.Show(
"沒有可用的通道!!您是否已經啓動服務器端?");
                
return;
            }

            
//打開通道
            ChannelHandle = HikVisionSDK.ChannelOpen(0);
            
//設置編碼幀結構、幀率
            HikVisionSDK.SetIBPMode(ChannelHandle, 1002125);
            
//設置編碼圖像質量
            HikVisionSDK.SetDefaultQuant(ChannelHandle, 151520);

            
//視頻預覽
            StartVideoPreview();
        }

        
/// <summary>
        
/// 視頻預覽
        
/// </summary>
        private void StartVideoPreview()
        {
            Rectangle rect 
= panel1.ClientRectangle;
            HikVisionSDK.StartVideoPreview(ChannelHandle, panel1.Handle, 
ref rect, false, (int)TypeVideoFormat.vdfRGB16, 25);
        }

        
/// <summary>
        
/// 窗體移動
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>
        private void Form2_Move(object sender, EventArgs e)
        {
            HikVisionSDK.StopVideoPreview(ChannelHandle);
            StartVideoPreview();
        }

        
#endregion
複製代碼

          代碼說明:

               1.     僅僅實現服務器端的預覽代碼並不多,這也是在VC++ Demo中不斷註釋代碼、在已經成功完成大部分功能的基礎上才試出來的,可見預覽和服務器啓動是相對獨立的。

               2.     Form2_Move是窗體移動時執行的,在VC++的也是在窗體移動中進行了同樣處理,否則你一移動窗體會出現難看的一幕呢 : )

               3.     StartVideoPreview的參數RECT *rect 直接使用Rectangle結構體即可。

               4.     panel1是窗體是的一個面板Panel。

     三、讓客戶端連接並預覽

          C# Code:

複製代碼
        //將委託聲明爲成員變量!!
        STREAM_DIRECT_READ_CALLBACK sdrc;

        
/// <summary>
        
/// 預覽並客戶端連接
        
/// </summary>
        private void PreviewAndClientConnect()
        {

            sdrc 
= new STREAM_DIRECT_READ_CALLBACK(STREAM_DIRECT_READ_CALLBACK1);

            
//[必須]註冊編碼圖像數據流直接讀取回調函數
            HikVisionSDK.RegisterStreamDirectReadCallback(sdrc, this.Handle);

            
//[必須]啓動服務端
            HikServer.MP4_ServerSetStart(new StartCap(StartCap));
            
//HikServer.MP4_ServerSetStop(sc);

            
//HikServer.MP4_ServerSetIBPMode(new SetIBP(SetIBP));
            
//[必須]設置回調,重新生成一個I幀
            HikServer.MP4_ServerSetCapIFrame(new MakeIFrame(MakeIFrame));

            
//HikServer.MP4_ServerSetTTL(64);
            
//HikServer.MP4_ServerSetNetPort(5050, 6050);

            PSERVER_VIDEOINFO videoInfo 
= new PSERVER_VIDEOINFO();
            
//初始化
            videoInfo.m_datatype = new byte[64];
            
//設置發送緩衝區大小
            HikServer.MP4_ServerSetBufNum((ushort)0, (ushort)90);
            videoInfo.m_datatype[
0= (byte)ChannelDataType.SMALLPIC;
            videoInfo.m_channum 
= (byte)1;
            videoInfo.m_waittime 
= 5;

            
//設置每個通道的最大用戶數量
            
//HikServer.MP4_ServerMaxUser(0, 24);

            
if (HikServer.MP4_ServerStart(ref videoInfo) == 0)
            {
                MessageBox.Show(
"服務端啓動錯誤!!");
            }

            
//開啓視頻預覽
            StartVideoPreview();
        }



        
#region 回調函數

        
public void StartCap(int port)
        {
            HikVisionSDK.StartVideoCapture(ChannelHandle);
        }

        
public void MakeIFrame(ulong port)
        {
            HikVisionSDK.CaptureIFrame(ChannelHandle);
        }

        
public int STREAM_DIRECT_READ_CALLBACK1(int channelNum, IntPtr DataBuf, int Length, FrameType_t frameType, IntPtr context)
        {
            
int status = 0;
            HikServer.MP4_ServerWriteDataEx(channelNum, DataBuf, Length, (
int)frameType, status, 0);
            
return 0;
        }

        
#endregion
複製代碼

          代碼說明:

               1.     將Form2_Load中最後一行代碼StartVideoPreview替換成PreviewAndClientConnect調用即可。

               2.     調用註釋前面帶了"[必須]"的方法是必須調用的,而被我的註釋掉的方法參照源代碼可以加也可以不加,因爲他是有默認設置的。

               3.     MakeIFrame這個回調函數是客戶端連接服務器的關鍵,如果沒有執行這個回調客戶端將不能夠連接並顯示畫面!

               4.     STREAM_DIRECT_READ_CALLBACK1回調函數在VC++代碼說明裏面已經說明了,因爲本章不寫視頻存儲,所以把其他代碼都註釋掉了,只管往內存寫數據就行了。

 

注意

     1.     StartVideoPreview的參數用結構體RECT會報錯,直接使用Rectangle結構體即可。

     2.     使用GetDspCount總是隻返回可用的Dsp數量,而用GetTotalDSPs可以獲取所有的Dsp數量。

     3.     再強調一遍,雖然我這裏沒有把委託實例化成 成員變量,也能調試通過,但是強烈建議您把這些都寫成 成員變量然後在窗體初始化時初始化!

     4.     本文是後續服務器端文章的基礎,務必細心調試,我敢說如果本文的功能你達到了——你的服務器端可以說完成了60%!!

 

修改記錄

     1.     2009-3-30

          將STREAM_DIRECT_READ_CALLBACK聲明爲成員變量,發現不聲明成成員變量在VS裏面調試可以運行通過(有時候),但是直接運行exe文件會報內存出錯!!

 

結束

     這篇文章在我研究的時候花了將近1個多星期,主要症狀就是能預覽,客戶端死活都看不到畫面,能連接!!甚至找了VC++牛人(不會C#)幫忙分析了都沒能出來,不過倒是幫我弄得能調試源代碼了,也是在無意中從頭到尾整理代碼的時候出來的(得到上司提醒整理代碼),極度興奮!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章