前言
在客户端相关的文章还没有写出来的时候,服务器端已经差不多了,没有很及时的把文章一篇接一篇的写是有理由的——有些功能我项目中暂时没有加入,只是对照api知道有这个功能,边写文章边做例子,这样一来发现有些api封装的不对,所以把这系列的文章写的速度都放慢了,以求尽量每一篇文章都正确。当然还是免不了找借口说太忙,现在在写播放器部分的代码,进展目前看来还顺利: )
注意
本系列文章限于学习交流,注重过程,由于涉及公司,所以不提供源代码下载,非常抱歉!!但是请大家放心,核心、实现以及其他能够贴出来的代码我都会贴出来,并且争取尽所能的回答留言里的每一个问题,感谢大家关注,欢迎交流 :)
系列
推荐文章
正文
一、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
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);
mp4_serversetbufnum(i,80);
videoinfo.m_datatype[i] = dialing;
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){
}
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)
mp4_serverwritedataex(channelnum,(unsigned char *)databuf, length,frametype,status,1);
}else
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("没有可用的通道!!您是否已经启动服务器端?");
//打开通道
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);
/// 窗体移动
/// <param name="sender"></param>
/// <param name="e"></param>
private void form2_move(object sender, eventargs e)
hikvisionsdk.stopvideopreview(channelhandle);

代码说明:
1. 仅仅实现服务器端的预览代码并不多,这也是在vc++ demo中不断注释代码、在已经成功完成大部分功能的基础上才试出来的,可见预览和服务器启动是相对独立的。
2. form2_move是窗体移动时执行的,在vc++的也是在窗体移动中进行了同样处理,否则你一移动窗体会出现难看的一幕呢 : )
3. startvideopreview的参数rect *rect 直接使用rectangle结构体即可。
4. panel1是窗体是的一个面板panel。
三、让客户端连接并预览

//将委托声明为成员变量!!
stream_direct_read_callback sdrc;
/// 预览并客户端连接
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("服务端启动错误!!");
//开启视频预览
#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;

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