筆者序:也許在寫這編文章時,有很多朋友正被老板要求做類似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的功能真是強大喲,哈哈。
歡迎大家光臨《黑雨共享軟件》,謝謝大家對中國共享軟件的支持
http://brsoft.0033.cn 
http://brsoft.008.net
新聞熱點
疑難解答