视频广播和点播程序
本文目录
一:前言
二:Mpeg1文件格式
三:网络数据广播
四:服务端程序编写
五:客户端程序编写
六:后记
一:前言
本文将介绍如何用Delphi编写视频广播(DVB)和点播(VOD)程序.内容将涉及网络数据广播和Mpeg1文件格式.希望通过本文大家可以知道流行的流媒体是怎么一回事.
我们先来了解一下什么叫DVB和VOD.DVB就是视频广播,表现形式为服务端程序打开一个视频文件播放,同一个网络内的客户端程序都可以接收到,节目的播放位置由服务端控制,客户端是被动接收的,不能改变播放位置.而VOD则刚好相反,大量的视频文件放在服务端电脑上,由客户端选择播放.播放过程中可以随时改变播放进度.无论是DVB还是VOD,它们在本质上都有共同点:就是把数据还原为影像.
关于网络播放,大概有以下几种形式:一种是基于文件共享方式,这种方式我以前已经写过一篇<<用Delphi在局域网中实现网上影院>>,这里不再详述.另外一种是基于文件切割,我在<<谈Delphi编程中文件格式的应用>>也有提到,就是把一个大的文件切割成多个小文件广播出去.这两种方法都有它的致命弱点.在第一种方法里,如果文件是在光盘里面的,那么几个人一起读的话就把电脑弄死了.即使是在硬盘里面,人数一多都会导致系统变慢.第二种方法,先不说接收文件存盘再播放有延时问题,而且多个文件之间无缝切换也是很麻烦的事情.如果客户端是无盘工作站,则完全不能用.
解决上面的问题就是用流来实现.一种方法就是服务端以流的方式读取文件(关于流的操作技巧,请参阅我以前写的<<谈Delphi编程中“流”的应用>>一文),然后在数据前面添加形如以下的数据:
var
strHeader:String;
begin
strHeader:= 'HTTP/1.1 200 OK' + EOL ;
strHeader:= strHeader + 'Server: Lovejingtao Web Server/1.0.0' + EOL ;
strHeader:= strHeader + 'Content-Type: application/octet-stream' + EOL ;
strHeader:= strHeader + 'Accept-Ranges: bytes' + EOL ;
strHeader:= strHeader + 'ETag: "05fa3dd93a9bd1:889" '+ EOL ;
strHeader:= strHeader + 'Content-Length: 1000' + EOL ;
strHeader:= strHeader + EOL ;
......
end;
客户端用系统本身带的Mediaplay来接收,接收地址象以下这种格式:http://192.168.0.1:8080.说白了,就是伪装Mediaplay Server服务端而已.
第二种方法就是本文要讲述的方法.其实这个方法与上面的是一样的.不同的是上面的格式之类是系统集成的,我们无法改变.而这种方法需要自己进行控制.
DVB的原理如下:服务端读取文件,然后向整个网络广播或者组播,客户端接收到数据后,放到内存中播放即可.点播的核心其实也是一样,也需要把数据转化为影像.我们把这个将数据还原为影像的过程称为"解码",大家经常听到的"解码器"就是这个意思,当然,解码器是有硬解码和软解码之分的.市面上有各种各样的解码器,RealPlay公司的,微软的等等.实际上很多播放软件都是调用别人的解码库而已,所以我们说豪杰公司的<<超级解霸>>很牛,因为它的解码器是自己写的.可能你也可以写一个功能跟它相近的播放器,但是我想解码器未必是可以人人都自己做的.顺便提一句:Delphi带的那个Mediaplay控件是基于MCI方式的,在核心层面来说实际上只不过是调用Windows本身的API而已.
本文用的解码器是基于微软的DirectxShow开发包,由那个Memfile改写而成.已经封装成DLL形式,这样一来就可以方便的在VC,Delphi,Vb等程序中调用.共提供了23个函数给大家调用.声明如下:
unit Mpeg1DecodeDll;
interface
uses Windows;
const DllName='Mpeg1Decode.dll';
function Mpeg1Decode_Init():bool;stdcall;external DllName;
function Mpeg1Decode_OpenVideo(hWnd:Thandle;r:TRECT;iSeek:DWORD=0):bool;stdcall;external DllName;
function Mpeg1Decode_PlayVideo():Bool;stdcall;external DllName;
function Mpeg1Decode_PauseVideo():Bool;stdcall;external DllName;
function Mpeg1Decode_ResumeVideo():Bool;stdcall;external DllName;
function Mpeg1Decode_StopVideo():Bool;stdcall;external DllName;
function Mpeg1Decode_CloseVideo():Bool;stdcall;external DllName;
function Mpeg1Decode_UnInit():Bool;stdcall;external DllName;
function Mpeg1Decode_SendBuf(buf:pbyte;lbufsize:longlong):DWORD;stdcall;external DllName;
function Mpeg1Decode_ReSizeWindowRect(r:TRect):Bool;stdcall;external DllName;
function Mpeg1Decode_SetHWND(hwnd:Thandle):Bool;stdcall;external DllName;
function Mpeg1Decode_SetNewHWND(hwnd:Thandle):Bool;stdcall;external DllName;
function Mpeg1Decode_SetWindowRect(r:Trect):Bool;stdcall;external DllName;
function Mpeg1Decode_FullScreenVideo(bFull:BOOL):Bool;stdcall;external DllName;
procedure Mpeg1Decode_About();stdcall;external DllName;
procedure Mpeg1Decode_PopupAudioPropDlg(hwnd:Thandle);stdcall;external DllName;
function Mpeg1Decode_GetMinimalVolume:DWORD;stdcall;external DllName;
function Mpeg1Decode_GetMaximalVolume:DWORD;stdcall;external DllName;
function Mpeg1Decode_GetCurrentVolume:DWORD;stdcall;external DllName;
procedure Mpeg1Decode_SetCurrentVolume(dwValue:DWORD);stdcall;external DllName;
function Mpeg1Decode_GetMpegFilePacketHead(FileName:Pchar;dwPackStartPos:PDWORD):Bool;stdcall;external DllName;
function Mpeg1Decode_CheckIsDatFormat(FileName:Pchar):Bool;stdcall;external DllName;
function Mpeg1Decode_CheckIsMpegFile(FileName:Pchar):Bool;stdcall;external DllName;
implementation
end.
在程序中使用这个Dll,首先调用Mpeg1Decode_Init来完成初始化COM对象,分配内存等工作.然后调用Mpeg1Decode_OpenVideo(hWnd:Thandle;r:TRECT;iSeek:DWORD=0)来指定播放的窗口句柄和大小尺寸.最后一个参数是要跳过的字节数.然后用Mpeg1Decode_SendBuf(buf:pbyte;lbufsize:longlong)来将Mpeg1数据发送到缓冲区,调用Mpeg1Decode_PlayVideo进行播放.播放结束后一次调用Mpeg1Decode_StopVideo,Mpeg1Decode_CloseVideo和Mpeg1Decode_UnInit进行释放资源之类的工作.
二:Mpeg1文件格式
Mpeg1文件分为两种,一种是纯Mpeg文件,另外一种是Dat格式的VCD文件.实际上,Dat文件也是Mpeg1文件,Dat的文件由文件头和数据两部分组成,从文件头位置开始,以2352字节为一个数据包.而纯Mpeg文件是没有那个头的,它是一系列的2324字节的数据包组成.将Dat文件转换为Mpeg文件很简单,从那个头开始,每个数据包只取前面的2324字节即可.我们下面用Delphi来写一个类好了.
unit Mpeg1DataFormat;
interface
uses
Windows,SysUtils,Classes,Mpeg1DecodeDll;
const
DatFramSize=2352;//Dat文件每个包大小
MpgFramSize=2324;//Mpg文件每个包大小
iOddCount=DatFramSize-MpgFramSize;//两种包相差值
type
TMpeg1DataFormat = class(TObject)
private
hFile:THandle;
FileName:String;
PackHead:integer;
IsVcd:Boolean;
function CheckIsVcd(const FileName:String):Boolean; //判断是否是DAT文件
function GetPacketHead(const FileName:String):integer; //取得文件头位置
function GetFileSize(const FileName: string): LongInt;//取文件大小
protected
public
FileSize:LongInt;
iFramCout:integer;
function GetMpegDateToByte(Buffer:PByte;const iFramSeek:integer=0):Boolean;
Constructor Create(const StrFilename:String);
Destructor Destroy; override;
end;
implementation
function TMpeg1DataFormat.GetFileSize(const FileName: string): LongInt;
var
SearchRec: TSearchRec;
begin
if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then
Result := SearchRec.Size
else Result := 0;
end;
constructor TMpeg1DataFormat.Create(const StrFilename:String);
begin
inherited Create;
FileName:=StrFilename;
FileSize:=GetFileSize(FileName);
PackHead:=GetPacketHead(FileName);
IsVcd:=CheckIsVcd(FileName);
//建立读文件句柄
hFile := CreateFile(Pchar(FileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
if hFile = INVALID_HANDLE_VALUE then Exit;
if IsVcd then
iFramCout:=(FileSize-PackHead) div DatFramSize
else
iFramCout:=(FileSize) div MpgFramSize
end;
destructor TMpeg1DataFormat.Destroy;
begin
CloseHandle(hFile);
inherited;
end;
function TMpeg1DataFormat.CheckIsVcd(const FileName:String):Boolean;
begin
Result:=Mpeg1Decode_CheckIsDatFormat(Pchar(FileName));
end;
function TMpeg1DataFormat.GetPacketHead(const FileName:String):integer;
var
i:PDWORD;
begin
new(i);
Mpeg1Decode_GetMpegFilePacketHead(Pchar(FileName),i);
Result:=i^;
Dispose(i);
end;
function TMpeg1DataFormat.GetMpegDateToByte(Buffer:PByte;const iFramSeek:integer=0):Boolean;
var
re:BOOL;
dwBytesRead:DWORD;
FramSize:integer;
begin
Result:=False;
if not(FileExists(FileName)) then Exit;
if IsVcd then FramSize:=DatFramSize else FramSize:=MpgFramSize;
if FileSize<(PackHead+FramSize*iFramSeek) then Exit;
if FileSeek(hFile,PackHead+FramSize*iFramSeek,soFromBeginning)=-1 then Exit;
re := ReadFile(hFile,Buffer^,MpgFramSize,dwBytesRead,nil);
if dwBytesRead<>MpgFramSize then Exit;
if re<>TRUE then Exit;//读文件失败的时候
Result:=True;
end;
end.
三:网络数据广播
首先要指出的是,广播是只有UDP协议才支持的,TCP是无法进行广播的.广播分为两种,一种是directed broadcast.<<谈Delphi编程中“流”的应用>>实际应用之三:利用流制作自己的OICQ中已经说的很详细了,下面摘录下来给大家:UDP协议还有一个很大的好处就是可以广播,就是说处于一个网段的都可以接收到信息而不必指定具体的IP地址。网段一般分A、B、C三类,
1~126.XXX.XXX.XXX (A类网) :广播地址为XXX.255.255.255
128~191.XXX.XXX.XXX(B类网):广播地址为XXX.XXX.255.255
192~254.XXX.XXX.XXX(C类网):广播地址为XXX.XXX.XXX.255
比如说三台计算机192.168.0.1、192.168.0.10、192.168.0.18,发送信息时只要指定IP地址为192.168.0.255就可以实现广播了。下面给出一个转换IP为广播IP的函数,快拿去完善自己的OICQ吧^-^.
Function Trun_ip(S:string):string;
var
s1,s2,s3,ss,sss,Head:string;
n,m:integer;
begin
sss:=S;
n:=pos('.',s);
s1:=copy(s,1,n);
m:=length(s1);
delete(s,1,m);
Head:=copy(s1,1,(length(s1)-1));
n:=pos('.',s);
s2:=copy(s,1,n);
m:=length(s2);
delete(s,1,m);
n:=pos('.',s);
s3:=copy(s,1,n);
m:=length(s3);
delete(s,1,m);
ss:=sss;
if strtoint(Head) in [1..126] then ss:=s1+'255.255.255'; //1~126.255.255.255 (A类网)
if strtoint(Head) in [128..191] then ss:=s1+s2+'255.255';//128~191.XXX.255.255(B类网)
if strtoint(Head) in [192..254] then ss:=s1+s2+s3+'255'; //192~254.XXX.XXX.255(C类网)
Result:=ss;
end;
另外一种是limited broadcast,广播地址是255.255.255.255.它的好处是只要在同一子网中的主机,就可以收到这种广播,而不必非要在统一逻辑子网中.例如,如果你的地址是192.168.0.1,那么这种广播,地址是192.202.30.17的主机也能收到.本文将使用此种广播.
在Delphi中使用UDP广播可以用控件,也可以API.本文中将完全使用API来实现.