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++自带的例子进行调试编写。

你可能感兴趣的:(服务器)