天天看點

C# 視訊監控系列(8):伺服器端——預覽和可被用戶端連接配接

一、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

            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(1, 1000, 0);

    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);

            MP4_ServerSetBufNum(i,80);

            videoinfo.m_datatype[i] = DIALING;

            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){

     }

     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)

        MP4_ServerWriteDataEx(channelNum,(unsigned char *)DataBuf, Length,frameType,status,1);    

    }else 

    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(10, 10, 10));

    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("沒有可用的通道!!您是否已經啟動伺服器端?");

            //打開通道

            ChannelHandle = HikVisionSDK.ChannelOpen(0);

            //設定編碼幀結構、幀率

            HikVisionSDK.SetIBPMode(ChannelHandle, 100, 2, 1, 25);

            //設定編碼圖像品質

            HikVisionSDK.SetDefaultQuant(ChannelHandle, 15, 15, 20);

            //視訊預覽

            StartVideoPreview();

        /// <summary>

        /// 視訊預覽

        /// </summary>

        private void StartVideoPreview()

            Rectangle rect = panel1.ClientRectangle;

            HikVisionSDK.StartVideoPreview(ChannelHandle, panel1.Handle, ref rect, false, (int)TypeVideoFormat.vdfRGB16, 25);

        /// 窗體移動

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void Form2_Move(object sender, EventArgs e)

            HikVisionSDK.StopVideoPreview(ChannelHandle);

          代碼說明:

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

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

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

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

三、讓用戶端連接配接并預覽

        //将委托聲明為成員變量!!

        STREAM_DIRECT_READ_CALLBACK sdrc;

        /// 預覽并用戶端連接配接

        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("服務端啟動錯誤!!");

            //開啟視訊預覽

        #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;

               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%!!

繼續閱讀