在MSN、QQ等聊天類的應用程序中,都應用到了網絡視頻技術。Delphi使用Object Pascal語言是一種完全面向對象語言,可以開發出靈活強大的程序,開發網絡視頻程序也不在話下。一個完整的網絡視頻程序應包括以下幾個關鍵技術:視頻捕獲、視頻壓縮與解壓、數據傳輸。
一、視頻獲捕
1.基本概念
微軟為軟件開發人員提供了一個專門用於視頻捕獲的VFW (Video for Windows) SDK,為在Windows系統中實現視頻捕獲提供了標准的接口,從而大大方便了視頻捕獲程序的開發。由於VFW SDK只有VC和VB版,沒有Delphi版,因此需要在Delphi中重新聲明DLL中的各個函數和變量(可以參考MSDN中的VC的函數聲明以及變量定義,也可以從網上下載寫好的頭文件vfw.pas)。
VFW是Microsoft 1992年推出的關於數字視頻的一個軟件包,它能使應用程序數字化並播放從傳統模擬視頻源得到的視頻剪輯。VFW的一個關鍵思想是播放時不需要專用硬件,為了解決數字視頻數據量大的問題,需要對數據進行壓縮。它引進了一種叫AVI的文件標准,該標准未規定如何對視頻進行捕獲、壓縮及播放,僅規定視頻和音頻該如何存儲在硬盤上,在AVI文件中交替存儲視頻幀和與之相匹配的音頻數據。VFW給程序員提供VBX和AVICap窗口類的高級編程工具,使程序員能通過發送消息或設置屬性來捕獲、播放和編輯視頻剪輯。
2.AVICap編程
AVICap支持實時的視頻流捕獲和單幀捕獲並提供對視頻源的控制,它能直接訪問視頻緩沖區,不需要生成中間文件,實時性很強,效率很高。同時,它也可將數字視頻捕獲到文件。AVICap編程的基本步驟包括:
第一步,創建AVICap窗口。通過capCreateCaptureWindow函數創建一個捕獲窗,所有的捕獲操作及其設置都以它為基礎。窗口風格一般為WS_CHILD和WS_VISIBLE。
第二步,設置AVICap窗口的相關屬性。通過capPreviewRate函數設置視頻捕獲速率;通過capPreview函數或capOverlay函數設置顯示視頻時的模式(普通的攝像頭不能用overlay的方式);通過capSetVideoFormat函數設置視頻格式(包括長度、寬度等);通過capDlgVideoSource、capDlgVideoFormat、capDlgVideoCompression顯示控制視頻源、視頻格式、視頻壓縮的對話框。
第三步,定義回調函數。定義捕獲幀回調函數,獲得每一幀的數據,並對每一幀的數據進行處理,比如壓縮、傳輸到客戶端等;定義窗口狀態回調函數,獲得窗口的狀態;定義錯誤回調函數,獲得並處理錯誤信息。
3.相關控件
雖然利用上面介紹的API可以實現視頻捕獲編程,但如果將這些API封裝成一個控件則編程更為方便,這樣的控件可以從常用Delphi網站找到。本文以TvideoCap控件為例,實現視頻捕獲。
(1)相關屬性及方法
DriverIndex該屬性是用來指定視頻捕獲設備序號,從0開始。
DriverOpen該屬性是用來確定是否打開指定的視頻捕獲設備。設置為True表示打開,False表示關閉。
CapToFile該屬性是用來確定捕獲的同時是否將捕獲的畫面保存成AVI格式的視頻文件。設置為True表示保存,False表示不保存。
VideoPreview該屬性是確定捕獲的同時是否預覽。設置為True表示預覽,False表示不預覽。
StartCapture該方法是用來捕獲視頻數據,執行該方法后才會觸發相關事件。
其他的屬性和方法這里就不一一介紹。
(2)相關事件
OnVideoStream當捕獲視頻數據時觸發該事件,在這里可以獲得每一幀的數據,進行相關處理,發送到客戶端。
二、視頻壓縮與解壓
通過AVICap窗口捕獲的每一幀的數據是以BMP(RAW)文件格式存放的,若直接進行傳輸,數據量非常大,對網絡的帶寬要求非常高,因此在傳輸之前必須對每一幀的數據進行壓縮處理后再進行傳輸。具體步驟:
第一步,安裝視頻壓縮引擎。媒體播放器軟件都帶有壓縮引擎,也可以從網上下載單獨的解壓縮引擎,比如MPEG4或DIVX等。
第二步,初始化壓縮引擎。選擇壓縮引擎,獲得壓縮引擎的支持,確定輸入、輸出格式,設置壓縮器。
第三步,壓縮幀數據。通過指定的壓縮引擎,對獲取的每一幀數據進行壓縮。
解壓的過程與壓縮的過程類似,通過選擇相對應的解壓引擎,將壓縮的數據解壓,以便於回放。
三、數據傳輸
1.基本概念
計算機在傳輸數據時有兩種方式:分別是TCP(Transmission Control Protocol,傳輸控制協議)及UDP(User Datagram Protocol,用戶數據報協議),兩者分別因數據傳輸的不同請求而提供不同的數據傳輸方式。
(1) TCP協議
TCP是一個基於連接的通信協議,主要目的是提供大量數據傳輸並確保其傳輸無誤,因此提供錯誤檢查、數據復原及數據重傳等機制。TCP在傳輸數據之前,會先在主機間(例如主控端與被控端)創建連接。根據此連接,數據可在計算機間相互傳輸,即所謂的雙向傳輸模式。
(2) UDP協議
UDP是一個非連接式的通信協議,主要目的在於傳輸少量的數據。與TCP不同的是,TCP在傳輸之前必須創建連接,而UDP不需要,只要設置計算機間的IP及使用相同的端口,就可以相互傳輸數據。因此UDP只提供單向的數據傳輸,即所謂的單向無連接傳輸模式。
由於UDP不需要先創建連接,節省了TCP創建連接所需的時間,所以適合在主機間進行單向的數據傳輸。由於視頻數據的傳輸對於實時性要求很高,即使傳輸過程中有個別幀的數據有錯,也不會影響整個視頻的效果,故本文將會詳細介紹如何通過UDP實現視頻數據的傳輸。
2.控件及相關內容介紹
在Delphi中對於UDP及TCP都提供了很好的支持,而且將它們封裝起來。開發人員無須知道協議的具體實現細節,而只要使用Delphi提供的TIdUDPServer元件(在Indy Servers頁)即可完成相應的功能。下面我們一起來認識一下這個元件。
(1) 相關屬性
DefaultPort該屬性是用來指定作為客戶端時要打開的端口號,也就是通過該端口來接收數據。
Active該屬性是用來打開指定的端口號,設置為True表示打開端口,False表示關閉端口。
BroadcastEnabled該屬性是用來設置是否用來實現廣播,設置為True表示可以廣播,False表示不能廣播。
(2) 相關事件
OnUDPRead當客戶端收到服務器端發來數據時觸發該事件,通過該事件我們可以取得服務器端發的每一幀的數據,以便在客戶端回放。
除了以上提到的一些屬性及事件外,TIdUDPServer還有一個重要的方法需要了解,那就是SendBuffer,通過該方法可以在服務器端向指定客戶端的指定端口發送數據。
四、具體實現
1.服務器端程序
新建一個項目,並將VFW.PAS包含到USES中。在窗體上加入一個控件TVideoCap,命名為VideoCap1,該控件用於視頻捕獲、顯示;放置一個TIdUDPServer,命名為VideoSender,用於傳輸視頻數據;放置兩個文本框,CilentIP設置客戶端IP,ClientPort設置發送到客戶端的端口;一個復選框CheckSend用來決定是否向客戶端傳輸視頻;放置兩個TButton控件,一個命名為“StartVideo”,用來打開視頻,另一個命名為“Closevideo”,用來關閉視頻,整體布局如圖1所示。
圖1 服務器端界面
定義發送數據包的結構:
type
VideoData=record
buf:array[0..8079] of byte; //壓縮后的視頻數據
Num:integer;//幀數據過大時,分幾個數據包發送,數據包在這一幀中的編號
IsLast:boolean;//是否是這一幀的最后一個數據包
end;
定義全局變量:
Var
FCV: TCOMPVARS;//幀壓縮結構
FInInfo: TBitmapInfo;//壓縮時輸入結構
FOutInfo: TBitmapInfo;//壓縮時輸出結構
FoutActSize: DWORD;//壓縮后幀數據的大小
Buffer:^byte; //壓縮后幀數據地址
Buf: array of Byte;// 壓縮后幀數據
主要代碼:
//填充BMP頭結構
procedure TForm1.FillBitmapStruc;
begin
FillChar(FInInfo.bmiHeader, SizeOf(TBitmapInfoHeader), 0);
with FInInfo.bmiHeader do
begin
biBitCount := 24;
biCompression := BI_RGB;
biHeight := 240;
biPlanes := 1;
biSize := SizeOf(TBitmapInfoHeader);
biWidth := 320;
end;
end;
//初始化壓縮引擎
procedure TForm1.InitCompressor;
begin
FillChar(FCV, SizeOf(FCV), 0);
with FCV do
begin
dwFlags := ICMF_COMPVARS_VALID;
cbSize := SizeOf(FCV);
fccHandler := mmioFOURCC('d','i','v','x'); //選擇壓縮引擎,這里選擇divx
fccType := ICTYPE_VIDEO;
hic := ICOpen(ICTYPE_VIDEO,fccHandler, ICMODE_COMPRESS);
lDataRate := 780;
lKey := 15;
lQ :=dword(ICQUALITY_DEFAULT);
if hic <> 0 then
begin
FillChar(FOutInfo, SizeOf(FOutInfo), 0);
ICCompressGetFormat(hic, @FInInfo, @FOutInfo);
FInInfo.bmiHeader.biCompression:=BI_RGB;
FOutInfo.bmiHeader.biCompression:=fccHandler;
ICSeqCompressFrameStart(@FCV, @FInInfo);
end;
end;
end;
//FormOnShow事件
procedure TForm1.FormShow(Sender: TObject);
begin
FillBitmapStruc;
InitCompressor;
//設置VideoCap1的相關屬性
VideoCap1.DriverIndex:=0;
VideoCap1.CapToFile:=false;
VideoCap1.DriverOpen:=true;
videocap1.VideoPreview:=true;
end;
//StartVideoOnClick事件
procedure TForm1.StartVideoClick(Sender: TObject);
begin
VideoCap1.StartCapture;
end;
// VideoCap1OnVideoStream事件
procedure TForm1.VideoCap1VideoStream(sender: TObject; lpVhdr: PVIDEOHDR);
var
KeyFrame:boolean;
MyVideo:VideoData;
i:integer;
begin
if CheckSend.Checked then
begin
FOutActSize:=0;
MyVideo.Num:=0;
//壓縮幀數據
Buffer:= ICSeqCompressFrame(@FCV, 0, lpVHdr^.lpData, @KeyFrame, @FOutActSize);
SetLength(buf,FOutActSize);
Move(Buffer^, Buf[0], FOutActSize);
//當幀數據太大時,分幾個數據包發送
while FOutActSize>8080 do
begin
MyVideo.Num:=MyVideo.Num+1;
MyVideo.IsLast:=false;
for i:=0 to 8079 do
begin
MyVideo.buf[i]:= buf[(MyVideo.Num-1)*8080+i];
end;
FOutActSize:=FOutActSize-8080;
// 向客戶端發送數據包
VideoSender.SendBuffer(CilentIP.text,strtoint(ClientPort.text),MyVideo,sizeof(MyVideo));
end;
if FOutActSize<8080 then
begin
MyVideo.Num:=MyVideo.Num+1;
MyVideo.IsLast:=true; //當前幀最后一個數據包
for i:=0 to FOutActSize do
begin
MyVideo.buf[i]:= buf[(MyVideo.Num-1)*8080+i];
end;
//向客戶端發送數據包
VideoSender.SendBuffer(CilentIP.text,strtoint(ClientPort.text),MyVideo,sizeof(MyVideo));
end;
end;
application.ProcessMessages;
end;
//StopVideoOnClick事件
procedure TForm1. StopVideoClick (Sender: TObject);
begin
VideoCap1.StopCapture;
end;
//FormOnClose事件
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if FCV.hic <> 0 then
begin
ICSeqCompressFrameEnd(@FCV);
ICCompressorFree(@FCV);
ICClose(FCV.hic);
end;
end;
2.客戶端程序
新建一個項目,並將VFW.PAS包含到USES中。在窗體上加入一個控件TImage,命名為Image1,該控件用於視頻顯示;放置一個TIdUDPServer,命名為VideoReceiver,用於接收視頻數據,DefaultPot屬性設置為6000,整體布局如圖2所示。
圖2 客戶器端界面
定義全局變量:
Var
FCV: TCOMPVARS;//幀壓縮結構
FInInfo: TBitmapInfo;//解壓時輸入結構
FOutInfo: TBitmapInfo;//解壓時輸出結構
FoutActSize: DWORD;//壓縮后幀數據的大小
Buf: array of Byte;// 未解壓時幀數據
myout:array[0..230399] of byte;// 解壓后幀數據
主要代碼:
//填充BMP頭結構
procedure TForm1.FillBitmapStruc;
begin
//略,見服務器端程序
end;
//初始化解壓縮引擎
procedure TForm1.InitDeCompressor;
begin
//略,見服務器端程序
end;
//FormOnShow事件
procedure TForm1.FormShow(Sender: TObject);
begin
FillBitmapStruc;
InitDeCompressor;
VideoReceiver.Active:=true;
end;
//VideoReceiverOnUDPRead事件
procedure TForm1.VideoReceiverUDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
var
RetVal:integer;
MyVideo:VideoData;
i:integer;
begin
RetVal:=-1;
AData.Position:=0;
//讀取數據
AData.ReadBuffer(MyVideo,sizeof(MyVideo));
if MyVideo.Num=1 then SetLength(buf,0);
SetLength(buf,(MyVideo.Num)*8080);
for i:=0 to 8079 do
begin
buf[(MyVideo.Num-1)*8080+i]:=MyVideo.buf[i];
end;
if MyVideo.IsLast then //當前幀最后一個數據包
begin
//解壓數據
RetVal := ICDeCompress(FCV.hic,0,@FoutInfo.bmiHeader,@Buf[0],@FOutInfo.bmiHeader,@myout);
if RetVal= ICERR_OK then
begin
//在Image1上畫出一幀圖像
StretchDIBits(Image1.Canvas.Handle,0,0,Image1.Width,Image1.Height,0,0,FOutInfo.bmiHeader.biWidth,FOutInfo.bmiHeader.biHeight,@myout,FOutInfo,0,SRCCOPY );
Image1.Repaint;
end;
end;
end;
//FormOnClose事件
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//略,同服務器端
end;
到此為止,整個網絡視頻程序就全部實現了。當然本程序的功能還有限,不過只要掌握了網絡視頻的的基本原理及相關知識后,根據需要在本程序的基礎上進行擴充,完全可以成為一個實用的網絡視頻軟件。本程序所有代碼在Delphi7、Windows 2000/XP 及局域網中調試運行通過。