筆者序:也許在寫這編文章時,有很多朋友正被老板要求做類似QQ一樣的視頻聊天軟件,在這裡,我把自己的一些經驗和代碼寫出來與大家一起分享,高手不要笑我哈!看了這編文章後,你也可以自己做一個簡單的網絡視頻通訊軟件,如果自己家裡上了網,就可以在公司和家人進行可視通訊了,多爽,不用給電話費了。
本例子使用的是簡的老技術(VFW),開發起來相對簡單,以下是Delphi代碼,你需要先加入VFW.PAS文件,沒有這個文件你可以在網上找一下。作者從Delphi4就開始編程,其實Delphi可以做很多事情,只是太多Delphi程序員沒有深專技術和思想,沒有超越自己,Delphi只是一個開發工具,代碼思想是的設計的精髓。
下面讓我們一起來講解一下:
在程序的開始,你需要用capCreateCaptureWindow來創建一個攝像頭句柄,
CapWnd := capCreateCaptureWindow('預覽窗口',WS_VISIBLE or WS_CHILD,0,0,320,240,PrevWnd,1);
在後面的參數:PrevWnd代表預覽窗口的句柄,你可以指定一個Panel的句柄;320和240代表了窗口的長寬。
if CapWnd = 0 then exit;
capDriverConnect(CapWnd,0); //連接攝像頭設備
capDlgVideoFormat(CapWnd); //顯示視頻設置對話框,進行配置視頻的大小、顏色位數等。
capGetVideoFormat(CapWnd,@BmpInInfo,sizeof(BITMAPINFO)); //取得視頻圖像數據頭,後面壓縮時需要用到
capPrevIEwRate(CapWnd, 33); //設置預覽視頻的頻率,33代表第秒30幀。
capPrevIEw(CapWnd, TRUE);
capSetCallbackOnFrame(CapWnd,FrameCallBack);
InitCaptureParams;
最後一句是設置視頻壓縮參數, 後面會進行說明。其中的capSetCallbackOnFrame(CapWnd,FrameCallBack)是設置每幀視頻數據的回調函數,我們就可以將回調時的視頻數據通過網絡進行傳輸,這樣的就實現了視頻聊天的核心了。
回調函數如下的格式:
function FrameCallBack(hWnd: HWND; lpVHdr: PVIDEOHDR): DWord; stdcall;
var
bKeyFrame : BOOL ;
Buf : PBYTE;
VideoData : TVIDEO_DATA;
OutActSize : dWord;
i : integer;
begin
OutActSize := BmpInInfo.bmiHeader.biSizeImage;
Buf := ICSeqCompressFrame(@CapVar,0,lpVHdr.lpData,@bKeyFrame,@OutActSize);
//在這裡, OutActSize代表壓縮後的視頻數據大小
// form1.Label3.Caption := 'Compressed size:'+inttostr(OutActSize);
//我用的是UDP方式, 因為UDP數據包大小限制, 所以我控制了數據大小, 超出的數據會發生丟幀
if (OutActSize <= sizeof(videodata.Buf) ) then
begin
zeromemory(@VideoData ,sizeof(TVIDEO_DATA));
//記錄是否為關鍵幀
VideoData.bKeyFrame:=bKeyFrame;
copymemory(@VideoData.Buf, Buf, OutActSize);
VideoData.SampleNum:=SampleNum; //我們可以記錄下幀數, 可以做擴展用
VideoData.BufSize:=OutActSize; //記錄數據大小, 傳輸時用
//在這裡, 你可以用你喜歡的網絡方式傳輸視頻數據,
//cc1.SendBuffer(VideoData,sizeof(TVIDEO_DATA)-SendBufferSize+Outactsize);
inc(SampleNum);
end;
result := 0;
end;
其中,PVIDEOHDR類型可以從VFW中看到其定義:
TVIDEOHDR = record
lpData : PBYTE; // 視頻數據buffer
dwBufferLength : DWord; // 數據buffer長度
dwBytesUsed : DWord;
dwTimeCaptured : DWord; // 時間長度(毫秒)
dwUser : DWord;
dwFlags : DWord;
dwReserved : array[0..3] of DWord;
end;
在回調函數中, 只用到了視頻函數: ICSeqCompressFrame,可以看到此函數傳入了CapVar參數,這個參數是由我們先前看到的InitCaptureParams函數產生,下面代碼來實現:
function InitCaptureParams : boolean;
begin
result := False;
//初始化CapVar
zeromemory(@CapVar,sizeof(TCOMPVARS));
CapVar.cbSize:=sizeof(CapVar); //必須指定cbSize為TCOMPVARS結構大小
CapVar.dwFlags:=ICMF_COMPVARS_VALID;
CapVar.cbState:=0;
//fccHandler代表壓縮編碼類型,我們使用的是DIVX的編碼器
CapVar.fccHandler:=mmioFOURCC('d','i','v','x');
CapVar.fccType:=ICTYPE_VIDEO;
//正式連接編碼器
CapVar.hic:=ICOpen(ICTYPE_VIDEO, CapVar.fccHandler, ICMODE_COMPRESS);
if (CapVar.hic>0) then
begin
OutFormatSize:=ICCompressGetFormatSize(CapVar.hic,@BmpInInfo.bmiHeader);
getmem(BmpOutInfo,OutFormatSize);
//我們可以通過初始化時得到的BmpInInfo來獲取壓縮傳出圖像頭BmpOutInfo
ICCompressGetFormat(CapVar.hic,@BmpInInfo.bmiHeader,@BmpOutInfo^.bmiHeader);
OutBufferSize:=ICCompressGetSize(CapVar.hic,@BmpInInfo.bmiHeader,@BmpOutInfo^.bmiHeader);
ICSeqCompressFrameStart(@CapVar, @BmpInInfo);
result := True;
end
else
begin
ShowMsg('請先安裝視頻壓縮編碼器');
Exit;
end
end;
使用之後,如果要斷開編碼器連接,是這樣調用的:
if (CapVar.hic > 0) then
begin
ICSeqCompressFrameEnd(@CapVar);
ICCompressorFree(@CapVar);
ICClose(CapVar.hic);
end;
於是,服務端的攝像頭數據捕捉連接就完成了,那麼對於客戶端是乍樣進行視頻數據解壓呢?這個問題當然還是通過IC函數解決,但你必須先把服務端上的BmpOutinfo和CapVar傳輸到客戶端才行。
接著,一起來看看客戶端的圖像顯示過程:
//先用取得的CapVar來連接視頻編碼器
CapVar.hic := ICOpen(CapVar.fccType,CapVar.fccHandler,ICMODE_DECOMPRESS);
//成功後,用服務器傳來的BmpOutInfo當作客戶端的BmpInInfo來取得解壓輸出的圖像頭BmpOutInfo
OutFormatSize:=ICDecompressGetFormatSize(CapVar.hic,@BmpInInfo.bmiHeader);
GetMem(BmpOutInfo,OutFormatSize);
zeromemory(BmpOutInfo,OutFormatSize);
ICDecompressGetFormat(CapVar.hic, @BmpInInfo.bmiHeader, @BmpOutInfo^.bmiHeader);
OutBufferSize:=BmpOutInfo^.bmiHeader.biSizeImage;
getmem(OutBuffer,OutBufferSize);
zeromemory(OutBuffer,OutBufferSize);
ICDecompressBegin(CapVar.hic,@BmpInInfo.bmiHeader, @BmpOutInfo^.bmiHeader);
最後,當然是視頻數據的解壓過程
if VIDEO_DATA.bKeyFrame then
Result:=ICDecompress(CapVar.hic,0,@BmpInInfo,@VIDEO_DATA.Buf,
@BmpOutInfo.bmiHeader,OutBuffer)
else
Result:=ICDecompress(CapVar.hic,ICDECOMPRESS_NOTKEYFRAME,@BmpInInfo,@VIDEO_DATA.Buf,
@BmpOutInfo.bmiHeader,OutBuffer);
if (Result=ICERR_OK) then
begin
SetDIBitsToDevice(Canvas.Handle,0,0,bmptmp.Width,bmptmp.Height,0,0,0,BmpOutInfo^.bmiHeader.biHeight ,
OutBuffer,BmpOutInfo^,DIB_RGB_COLORS);
end;
這樣,傳送過來的視頻數據變直接畫到了Canvas.Handle上了。
還忘記了服務端關閉攝像頭的方法,調用capDriverDisconnect(CapWnd) 就OK了。
全文就Over了,jasonke還要說的就是,這個方法是用的微軟的老函數,不過實現起來很簡單,相信會點API的都能開發出來,還有一種方法當然是用DirectShow了喲,這需要你開發Filter,要搞明白微軟的幾個接口,你可以看看DShowNetwork例子。這個方法也有很多C++的兄弟在痛苦的實現,想一想DirectShow的功能真是強大喲,哈哈。