您的位置:首页 > 编程语言 > C#

C# 视频监控系列(8):服务器端——预览和可被客户端连接

2009-03-11 10:20 676 查看
前言

在客户端相关的文章还没有写出来的时候,服务器端已经差不多了,没有很及时的把文章一篇接一篇的写是有理由的——有些功能我项目中暂时没有加入,只是对照API知道有这个功能,边写文章边做例子,这样一来发现有些API封装的不对,所以把这系列的文章写的速度都放慢了,以求尽量每一篇文章都正确。当然还是免不了找借口说太忙,现在在写播放器部分的代码,进展目前看来还顺利: )

注意

本系列文章限于学习交流,注重过程,由于涉及公司,所以不提供源代码下载,非常抱歉!!但是请大家放心,核心、实现以及其他能够贴出来的代码我都会贴出来,并且争取尽所能的回答留言里的每一个问题,感谢大家关注,欢迎交流 :)

系列

系列

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):服务器端——预览和可被客户端连接

推荐文章

1. 如果非托管代码需要多次调用托管代码中的委托,请将委托保存为成员变量。 极其重要的一篇文章,非常建议你把每一个委托都实例成成员变量,在正文中代码可以看到,很多执行委托的地方报错绝大部分都是这个原因!!

正文

一、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%!!

修改记录

1. 2009-3-30

将STREAM_DIRECT_READ_CALLBACK声明为成员变量,发现不声明成成员变量在VS里面调试可以运行通过(有时候),但是直接运行exe文件会报内存出错!!

结束

这篇文章在我研究的时候花了将近1个多星期,主要症状就是能预览,客户端死活都看不到画面,能连接!!甚至找了VC++牛人(不会C#)帮忙分析了都没能出来,不过倒是帮我弄得能调试源代码了,也是在无意中从头到尾整理代码的时候出来的(得到上司提醒整理代码),极度兴奋!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐