一、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( 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 ); } 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( 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( " 没有可用的通道!!您是否已经启动服务器端? " ); return ; } // 打开通道 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 ); } /// <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%!!
本文转自博客园农民伯伯的博客,原文链接:,如需转载请自行联系原博主。