前言
在客户端相关的文章还没有写出来的时候,服务器端已经差不多了,没有很及时的把文章一篇接一篇的写是有理由的——有些功能我项目中暂时没有加入,只是对照API知道有这个功能,边写文章边做例子,这样一来发现有些API封装的不对,所以把这系列的文章写的速度都放慢了,以求尽量每一篇文章都正确。当然还是免不了找借口说太忙,现在在写播放器部分的代码,进展目前看来还顺利: )
注意
本系列文章限于学习交流,注重过程,由于涉及公司,所以不提供源代码下载,非常抱歉!!但是请大家放心,核心、实现以及其他能够贴出来的代码我都会贴出来,并且争取尽所能的回答留言里的每一个问题,感谢大家关注,欢迎交流 :)
系列
系列
5. C# 视频监控系列(5):客户端——给服务器端发送字符串和录像(数据捕获)
6. C# 视频监控系列(6):服务器端——封装API(上) [HikServer.dll]
7. C# 视频监控系列(7):服务器端——封装API(下) [DS40xxSDK.dll]
8. C# 视频监控系列(8):服务器端——预览和可被客户端连接
推荐文章
1. 如果非托管代码需要多次调用托管代码中的委托,请将委托保存为成员变量。 极其重要的一篇文章,非常建议你把每一个委托都实例成成员变量,在正文中代码可以看到,很多执行委托的地方报错绝大部分都是这个原因!!
正文
一、VC++ Demo里关于这两个功能的实现和分析
基本上每段代码都可以从OnInitDialog这个方法开始分析
1.1. VC++ Code:
HikVisionDlg.cpp 的OnInitDialog方法中的关键代码
{
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 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方法
VideoWin.cpp的StartVideoPreview方法
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:
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%!!
修改记录
1. 2009-3-30
将STREAM_DIRECT_READ_CALLBACK声明为成员变量,发现不声明成成员变量在VS里面调试可以运行通过(有时候),但是直接运行exe文件会报内存出错!!
结束
这篇文章在我研究的时候花了将近1个多星期,主要症状就是能预览,客户端死活都看不到画面,能连接!!甚至找了VC++牛人(不会C#)帮忙分析了都没能出来,不过倒是帮我弄得能调试源代码了,也是在无意中从头到尾整理代码的时候出来的(得到上司提醒整理代码),极度兴奋!!
转载:http://www.cnblogs.com/over140/archive/2009/03/11/1397378.html