天天看點

C# 視訊監控系列(9):伺服器端——資料捕獲(抓圖 + 錄像)

一、抓圖

          這個功能沒有在VC++伺服器端找到對應的代碼,但是GOOGLE到了一段CSDN求助的代碼: 

int   ret=GetJpegImage(aa,bb,cc,dd);   

  if(ret==0)   

  {   

  CString   str;   

  str.Format("ch%02d_%s.jpg",iLastSelect,csStartTime);   

  FILE   *pFile=fopen(str.GetBuffer(0),"wb");//Buffer應該是個緩沖區   

  if(pFile)   

  fwrite(bb,cc,1,pFile);           //存儲圖像   

  fclose(pFile);   

          C#:

            byte[] imageBuf = new byte[704*576*2];

            int size = 704*576*2;

            HikVisionSDK.GetJpegImage(ChannelHandle, imageBuf, out size, 100);

            using (MemoryStream ms = new MemoryStream(imageBuf))

            {

                Image image = Image.FromStream(ms, true);

                image.Save("C:\\1.jpg");

            }

          注意GetJpegImage的參數說明!!并且請注意,由于這個示例,發現前面的(GetJpegImage/GetOriginalImage)API錯誤了,請你及時更新!!

public static extern int GetOriginalImage(IntPtr hChannelHandle, byte[] ImageBuf, out int Size);

public static extern int GetJpegImage(IntPtr hChannelHandle, byte[] ImageBuf, out int Size, uint nQuality);

          儲存為bmp的方法請自行嘗試,應該是差不多的: )

二、錄像

          關于錄像的檔案總共有三個部分,分别是檔案頭、資料流和檔案尾,這裡先給出代碼,然後再進行說明。

          VC++:

               CHKVisionDlg::OnStart()

        for(int i = 0; i < GetTotalDSPs(); i++){

            m_bDspPreset[i]=TRUE;

            if(m_bDspPreset[i]){

                char fileName[256];

                sprintf(fileName, "d:\\stream%d_%d.264", i, gFileNum++/GetTotalDSPs());

                gFileHandle[i] = _open(fileName, _O_CREAT | _O_BINARY | _O_WRONLY| _O_TRUNC, _S_IREAD | _S_IWRITE);

                if(gFileHandle[i] == -1){

                    TRACE("channel %d file open error\n,i");

                    return;

                }

                gChannelFrames[i] = 0;

                gChannelTotalLength[i] = 0;

                gChannelFramesLost[i] = 0;

                gChannelOverflow[i] = 0;

                gCurrentFileLen[i] = 0;

                _write(gFileHandle[i], FileHeader[i], FileHeaderLen);

                // could not be start again untill stopped first

                //m_bDspPreset[i] = FALSE;

                gCaptureStartedNum++;

                // let the threads have chance to run

                //Sleep(500);

            }else

                gFileHandle[i] = -1;

        }

               StreamDirectReadCallback

int __cdecl StreamDirectReadCallback(ULONG channelNum,void *DataBuf,DWORD Length,int frameType,void *context)

{

    //CHKVisionDlg * lpDlg = (CHKVisionDlg*)context;

    //return lpDlg->ProcCallBack(channelNum, DataBuf, Length, frameType);

    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 

        //_write(gFileHandle[channelNum], DataBuf, Length);

    return 0;

}

               CHKVisionDlg::OnStop()

                ASSERT(gFileHandle[i] != -1);

//                StopVideoCapture(ChannelHandle[i]);

                //lseek(gFileHandle[i], 0, SEEK_SET);

                //FRAMES_STATISTICS fs;

                //GetFramesStatistics(ChannelHandle[i], &fs);

                //ULONG frames = fs.AudioFrames + fs.VideoFrames;

                //TRACE("channel %i has %x frames written\n", i, frames);

#define END_CODE 0x00000002

                ULONG endCode = END_CODE;

                _write(gFileHandle[i], &endCode, sizeof(ULONG));

                _close(gFileHandle[i]);

///add v34

                if (bEncodeCifAndQcif[i])

                    _close(gFileHandleQcif[i]);

                gCaptureStartedNum--;

               代碼說明:

                    1.     從StartCap和StopCap的按鈕事件可以看得出主要實作寫檔案頭和檔案尾的功能,注意_write函數。

                    2.     而上一章我們講到了回調函數StreamDirectReadCallback,主要是将資料寫到記憶體中,從代碼能看出回調中是邊寫記憶體邊寫檔案的代碼,而且輸出就是.264檔案。由于回調從啟動開始(允許被用戶端通路),就一直不停的在調用這個回調,根據斷點調試可以看得出當frameType == PktSysHeader時表示的就是檔案頭,并且隻執行一次,這樣在點選StartCap按鈕時就直接将這個儲存的檔案頭的資料寫入檔案了,用UE打開.264的檔案可以發現前幾個字元總是以4HKH開頭的檔案。

                    3.     注意gFileHandle是一個檔案指針數組,檔案被打開後回調中就一直往這個檔案指針寫資料!!

        //用于存放頭檔案

        byte[] FileHeader;

        //檔案頭長度

        int FileHeaderLen;

        //是否開始捕獲檔案 0 未啟用 1 啟用

        volatile int CaptureState;

        /// <summary>

        /// 開始錄像

        /// </summary>

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

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

        private void btnStart_Click(object sender, EventArgs e)

        {

            //寫入頭檔案

            using (FileStream fs = new FileStream("C:\\hik.264", FileMode.Create))

                BinaryWriter bw = new BinaryWriter(fs);

                bw.Write(FileHeader);

                bw.Flush();

                bw.Close();

            CaptureState = 1;

        uint endCode = 0x00000002;

        /// 停止錄像

        private void btnStop_Click(object sender, EventArgs e)

            CaptureState = 0;

            using (FileStream fs = new FileStream("C:\\hik.264", FileMode.Append))

                bw.Write(endCode);

        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;

            int status = 0;

            if (frameType > 0)

                if (frameType == FrameType_t.PktSysHeader)

                {

                    FileHeader = new byte[Length];

                    Marshal.Copy(DataBuf, FileHeader, 0, Length);

                    FileHeaderLen = Length;

                if (frameType == FrameType_t.PktIFrames || frameType == FrameType_t.PktSubIFrames)

                    status = 1;

                else

                    status = 0;

                if (frameType == FrameType_t.PktMotionDetection || frameType == FrameType_t.PktOrigImage)

                    return 0;

            if (Length == 0)

                //TRACE("no data ?\n");

                return 0;

            if (frameType == FrameType_t.PktAudioFrames)

                WriterVideoCapture(Length, DataBuf);

                //寫檔案

                //    _write(gFileHandleQcif[channelNum],DataBuf,Length);

                //HikServer.MP4_ServerWriteDataEx(channelNum, DataBuf, Length, (int)frameType, status, 1);

                //    _write(gFileHandle[channelNum], DataBuf, Length);

                HikServer.MP4_ServerWriteDataEx(channelNum, DataBuf, Length, (int)frameType, status, 0);

            else if (frameType == FrameType_t.PktSubIFrames || frameType == FrameType_t.PktSubPFrames || frameType == FrameType_t.PktSubBBPFrames || frameType == FrameType_t.PktSubSysHeader)

                HikServer.MP4_ServerWriteDataEx(channelNum, DataBuf, Length, (int)frameType, status, 1);

            else

            return 0;

        /// 将資料流寫入視訊檔案

        /// <param name="length"></param>

        /// <param name="dataBuf"></param>

        private void WriterVideoCapture(int length, IntPtr dataBuf)

            if (CaptureState == 1)

                using (FileStream fs = new FileStream("C:\\hik.264", FileMode.Append))

                    BinaryWriter bw = new BinaryWriter(fs);

                    byte[] byteBuf = new byte[length];

                    Marshal.Copy(dataBuf, byteBuf, 0, length);

                    bw.Write(byteBuf);

                    bw.Flush();

                    bw.Close();

          代碼說明:

               1.     回調函數STREAM_DIRECT_READ_CALLBACK1是在上篇文章的基礎上修改的,也主要是參照的VC++的源代碼改寫的。

               2.     CaptureState變量主要用于STREAM_DIRECT_READ_CALLBACK1中控制是否寫檔案。

               3.     btnStart_Click與btnStop_Click分别代表界面上的開始錄像和停止錄像按鈕。

               4.     注意寫檔案的方式,開始錄像用FileMode.Create,持續寫入用FileMode.Append。

繼續閱讀