前言
在用戶端相關的文章還沒有寫出來的時候,伺服器端已經差不多了,沒有很及時的把文章一篇接一篇的寫是有理由的——有些功能我項目中暫時沒有加入,隻是對照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