C# 視頻監控系列(9):服務器端——數據捕獲(抓圖 + 錄像)

前言

     錄像功能是監控系統中最重要的功能之一,除了本文的功能實現外,還需要你自己考慮合適的存儲策略:存儲大小、時間段、存儲盤符等。

 

注意

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

 

系列

     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):服務器端——預覽和可被客戶端連接

     9.     C# 視頻監控系列(9):服務器端——數據捕獲(抓圖 + 錄像) 

 

推薦文章

     1.     《控工安防監控知識論壇》提供海康DS4000M/DS4000H卡在安裝驅動時常見問題解答等安防專業知識

 

正文

     一、抓圖

          這個功能沒有在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);   
複製代碼

          另外一段代碼:http://topic.csdn.net/t/20060721/09/4894821.html

          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){
             
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;
}
複製代碼

               CHKVisionDlg::OnStop()

複製代碼
        for(int i = 0; i < GetTotalDSPs(); i++){
            
if(m_bDspPreset[i]){
                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是一個文件指針數組,文件被打開後回調中就一直往這個文件指針寫數據!!

          C#:

複製代碼
        //用於存放頭文件
        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;

        
/// <summary>
        
/// 停止錄像
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>
        private void btnStop_Click(object sender, EventArgs e)
        {
            CaptureState 
= 0;
            
using (FileStream fs = new FileStream("C:\\hik.264", FileMode.Append))
            {
                BinaryWriter bw 
= new BinaryWriter(fs);
                bw.Write(endCode);
                bw.Close();
            }
        }

        
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)
            {
                
//    _write(gFileHandleQcif[channelNum],DataBuf,Length);
                HikServer.MP4_ServerWriteDataEx(channelNum, DataBuf, Length, (int)frameType, status, 1);
            }
            
else
            {
                WriterVideoCapture(Length, DataBuf);
                HikServer.MP4_ServerWriteDataEx(channelNum, DataBuf, Length, (
int)frameType, status, 0);
            }

            
return 0;

        }

        
/// <summary>
        
/// 將數據流寫入視頻文件
        
/// </summary>
        
/// <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_ClickbtnStop_Click分別代表界面上的開始錄像和停止錄像按鈕。

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

 

補充

     1.     錄像的時候務必考慮單錄像文件的大小以及磁盤空間不夠的問題,最好還能考慮下分時段監控等。

     2.     注意保存文件頭的變量FileHeader,如果分文件連續保存的話有可能出現第一個文件能播放,後面的都不能播放了,可能是文件頭變量的數據類型問題,你可以換byte[] -> IntPtr保存試試看。

     3.     自帶的示例裏面有播放器極其源碼,打開播放器,直接將.264文件拖拽到裏面就可以播放了;如果報錯那麼說明你的錄像有問題!!

 

結束

     雖然代碼都給出來了,但是裏面整個過程還是需要理解的,一定要配合VC++自帶的例子進行調試編寫

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