在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 及局域网中调试运行通过。

用Delphi实现网络视频编程的更多相关文章

  1. DELPHI下的SOCK编程(转)

    DELPHI下的SOCK编程      本文是写给公司新来的程序员的,算是一点培训的教材.本文不会涉及太多的编程细节,只是简单讲解在DELPHI下进行Winsock编程最好了解的知识. 题外话:我认为 ...

  2. DELPHI下的SOCK编程

     DELPHI下的SOCK编程(转自http://www.cnblogs.com/devcjq/articles/2325600.html) 本文是写给公司新来的程序员的,算是一点培训的教材.本文不会 ...

  3. Linux应用程序设计之网络基础编程

    1.TCP/IP协议概述 1.1.OSI参考模型及TCP/IP参考模型 OSI协议参考模型是基于国际标准化组织(ISO)的建议发展起来的,从上到下工分为7层:应用层,表示层,会话层,传输层,网络层,数 ...

  4. 深入Delphi下的DLL编程

    深入Delphi下的DLL编程 作者:岑心 引 言 相信有些计算机知识的朋友都应该听说过“DLL”.尤其是那些使用过windows操作系统的人,都应该有过多次重装系统的“悲惨”经历——无论再怎样小心, ...

  5. iOS网络高级编程:iPhone和iPad的企业应用开发之错误处理

    本章内容 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWluZ2h1YXdlbmthbmc=/font/5a6L5L2T/fontsize/400/fi ...

  6. UDP协议网络Socket编程(java实现C/S通信案例)

    我的博客园:https://www.cnblogs.com/chenzhenhong/p/13825286.html 我的CSDN博客:https://blog.csdn.net/Charzous/a ...

  7. 网络异步编程(C#)团购课

    新生命开发团队大石头讲解网络异步编程(C#) 内容:网络编程基础.IOCP.APM.SAEA 时长:2~3小时 价格:20元,20人及以上成团,http://item.taobao.com/item. ...

  8. Android 视频播放器 VideoView 的使用,播放本地视频 和 网络 视频

    1.布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:and ...

  9. Android Multimedia框架总结(二)MediaPlayer框架及播放网络视频案例

    前言:前面一篇我们介绍MediaPlayer相关方法,有人说,没有实际例子,看得不是很明白,今天在分析MediaPlayer时,顺带一个播放网络视频例子.可以自行试试.今天分析的都是下几篇介绍各个模块 ...

随机推荐

  1. LeetCode OJ-- Distinct Subsequences ** 递推

    https://oj.leetcode.com/problems/distinct-subsequences/ 对于string S 和 T求,T 是 S的几种子串. 首先想到了递归方法,列出递归公式 ...

  2. [Python Cookbook] Numpy: Multiple Ways to Create an Array

    Convert from list Apply np.array() method to convert a list to a numpy array: import numpy as np myl ...

  3. koa2 从入门到进阶之路 (三)

    之前的文章我们介绍了一下 koa 路由,get 传值,动态路由,本节我们看一下 koa 中间件 以及 koa 中间件的洋葱图执行流程. 一.什么是 Koa 的中间件 通俗的讲:中间件就是匹配路由之前或 ...

  4. 2016集训测试赛(二十四)Problem C: 棋盘控制

    Solution 场上的想法(显然是错的)是这样的: 我们假设棋子是一个一个地放置的, 考虑在放置棋子的过程中可能出现哪些状态. 我们令有序整数对\((i, j)\)表示总共控制了\(i\)行\(j\ ...

  5. 洛谷1373小a和uim之大逃离

    题目背景 小a和uim来到雨林中探险.突然一阵北风吹来,一片乌云从北部天边急涌过来,还伴着一道道闪电,一阵阵雷声.刹那间,狂风大作,乌云布满了天空,紧接着豆大的雨点从天空中打落下来,只见前方出现了一个 ...

  6. 对象第复制operator=

    类机制中有默认的对象复制操作符=,自定义对象复制需要注意一个问题,如果有遇到指针指向的资源是需要释放的,这时需要毫不留情释放,否则内存空间的泄露就不可避免.复制操作与拷贝构造函数的参数是一致的,只是在 ...

  7. AngularJS的稍复杂form验证

    代码下载:https://files.cnblogs.com/files/xiandedanteng/angularjsSoccerFormCheck.rar 代码: <!DOCTYPE HTM ...

  8. Android使用OKHttp3实现下载(断点续传、显示运行进度)

    OKHttp3是现在很流行的Android网络请求框架,那么怎样利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思 准备阶段 我们会用到OKHttp3来做网络请求,使用R ...

  9. 【LeetCode】Validate Binary Search Tree ——合法二叉树

    [题目] Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is defin ...

  10. 重读金典------高质量C编程指南(林锐)-------第四章 表达式和基本语句

    4.1 运算符的优先级   规则:如果代码行中的运算符比较多,可用括号确定操作顺序.if((a|b)&&(a&c)) 4.2   复合表达式 规则:不要编写太复杂的复合表达式 ...