最近一直忙于工作,要换工作了,所以一直没有时间写文章了,一直关心我blog的朋友们估计,又要说我不更新了,加上自从写了Directshow实现qq的音视频传输一文后,有很多朋友发email,询问相关的问题,正好我再网上看到这篇文章,贴到这里,我们一起学习吧,向本文的作者表示感谢,如果你觉得转载该文侵犯了您的权利,请及时通知我
基于
DirectShow
的
MPEG-4
视频传输系统的研究与实现
温小明
吴志刚
(东华大学计算机学院 上海 200051)
摘要
本文简单介绍了DirectShow技术,研究了利用DirectShow实现视频采集、压缩和网络传输技术。并利用第三方提供的编解码器实现了MPEG-4视频数据的网络传输系统,在该系统中利用RTP协议进行视频数据传输,同时实现了远端帧率的控制。
关键词 视频; 采集; 压缩; DirectShow; MPEG-4,RTP
1
引言
近年来,随着国民经济的发展,社会各个部门对于视频监视系统的需求越来越多。但目前的很多监视系统都跟具体的硬件相关,必须要具体的采集卡的支持才能实现。所以有必要开发一种具有通用性的视频监视系统,用普通的摄像头就能实现视频的采集。
基于
DirectShow
的开发能很灵活地控制音视频的效果,所以选择
DirectShow
这种可扩展性好的技术做开发对以后的应用升级很有帮助。
此外,为了实现流媒体传输控制的策略,流媒体的传输和回放也是应解决的问题之一。由
Microsoft
提供的
DirectShow
技术基于组件对象模型技术,支持宽松的格式变化,提供高品质的多媒体流回放。利用它可以在普通微机中实现流媒体的客户端处理,并可以提高系统的通用性和可扩展性。
对于视频数据的传输,压缩率是一个必须考虑到的因素。
MPEG-4
是由
ISO
和
IEC
的
MPEG
组制定的一个关于活动图像和声音的编码国际标准。它在基于内容的交互性、压缩率、通用访问能力等方面提供了一系列新的或改进的功能。
MPEG-4
视频在提供较好的图像质量的同时拥有较高的压缩率,适合于作为传输的图像压缩标准。
2
相关技术
2.1 DirectShow
技术简介
DirectShow
是
Microsoft
为开发高性能多媒体应用而开发的底层应用程序接口(
API
)
,
它是
DirectX
家族的核心成员之一。
DirectShow
自身是通过一种系统内置的或程序员开发的过滤器(
Filter
)来控制和处理多媒体数据的体系结构。该体系结构定义了如何处理和控制过滤器内部及相互之间的多媒体数据流。每个过滤器都有输入或输出针(
Pin
)
,
或两者都有。
过滤器(
Filter
)是
DirectShow
的基本组成部分,是
Filter Graph(
过滤器图
)
中最小的功能模块,
DirectShow
将多媒体数据的处理分离成不同的步骤,这些不同的步骤由相应的
Filter
去处理。这样我们可以把不同的过滤器搭配在一起达到我们要求的来处理多媒体数据。过滤器根据实现功能的不同大致可分为
3
类:
1
源过滤器(
Source Filters
)。源过滤器负责得到原始媒体数据。这些媒体数据的来源包括本地硬盘或网络上的媒体文件、各种采集卡等。
2
转换过滤器(
Transform Filters
)。转换过滤器的任务是处理从其他过滤器中接收的数据,经过一定的处理后再传递给下一个过滤器。编解码器就是典型的转换过滤器。
3
表现过滤器(
Rendering Filters
)。表现过滤器对接收到的数据进行最后的处理。它做的工作有:把媒体数据保存为文件、将数据发送到网络、显示视频、回放音频等
[1]
。
在
DirectShow
系统之上是应用程序
(Application)
。应用程序要按照程序所要实现的功能建立起相应的
Filter Graph ,
然后借助于
Filter Graph Manager
来控制整个数据的处理过程。
DirectShow
能在
Filter Graph
运行的时候接收到各种事件
,
并通过消息的方式发送到应用程序。这样就实现了应用程序与
DirectShow
系统之间的交互。
2.2 RTP/RTCP
协议介绍
实时传输协议
RTP(Realtime Transport Protocol)
是针对
Internet
上多媒体数据流的一个传输协议
,
1996
年由
IETF( Internet
工程任务组
)
的
AVT
小组作为
RFC1889
发布
AVT
小组后来对该文档进行了不断改进
,
于
2003
年
7
月提出了代替
RFC1889
的
RFC3550
。
RTP
充分体现了应用层分帧这一现代通信协议的设计思想,允许其用户了解、调整甚至制定连续媒体的打包方案,该协议被广泛用于
VoIP
、视频等实时媒体的传送。
RTP
协议包括
RTP
和
RTCP(RTP
控制协议
)
两个关系十分密切的子协议:
(1) RTP
协议-传输具有实时特性的数据;
(
2
)
RTCP
协议
—
监测
QoS
和传送参与传输者的信息。
RTP(
实时传输协议
)
通常工作在
UDP
的上层,从上层接收多媒体信息码流
(
如
MPEG-4
视频
)
,组装成
RTP
数据包
,
然后发送给下层
UDP
,相当于
OSI
的会话层,提供同步和排序服务。故
RTP
协议适用于传送连续性强的数据,如视频、音频等,并对网络引起的时延差错有一定的自适应能力。
RTCP
为实时控制协议,用于管理控制信息,如监视网络的延时和带宽,一旦所传输的多媒体信息的带宽发生变化,接收端则通知发送端,广播符号化识别码和编码参数,达到控制传输质量的目的。此外
,
如果底层网络支持多点传播的话,
RTP
还支持使用多点传播向多个目的端点发送数据。
RTP
协议具有如下特点
[5]
:
(
1
)灵活性
RTP
协议的数据报文和控制报文使用不同的端口,数据流和控制流分离,这样大大地提高了协议的灵活性,处理也简单。
(
2
)支持多播
如果下层网路支持,可以支持
多播。
(
3
)可扩展性
RTP
协议通常为一个具体的应用提供服务,通过一个具体的应用进程实现,而不作为
OSI
体系结构中单独的一层来实现,
RTP
只提供协议框架,开发者可以根据应用的具体要求对协议进行充分的扩展。
3
关键技术的实现
该系统的发送端实现思路如下:用
USB
摄像头采集数据,用
Divx 5.1.1 Codec
对采集到的数据进行
MPEG-4
的编码,然后连到一个发送
Filter
把编码后的数据发送出去。其
Filter Graph
如图
1
所示
:
图
1
发送端的
Filter Graph
接收端的实现思路如下
:
通过一个接收
Filter
接收发送端发送的数据,然后再用
Divx Decoder Filter
对接收到的数据进行解码。最后用
Video Renderer
把解码后的数据播放出来。其
Filter Graph
如图
2
所示:
图2
接收端的Filter Graph
3.1
数据采集及编码的实现
3.1.1
采集
Filter Graph
的实现
采集应用的
Filter Graph
一般比较复杂,而直接使用
Filter Graph Manager
上的
IGraphBuilder
接口构建这种
Filter Graph,
有时候难度又很大。为此,
DirectShow
特别提供了一个辅助组件
Capture Graph Builder,
来简化这种
Filter Graph
的创建。
首先是创建
Filter Graph Manager
组件,核心代码如下:
hr=CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,(void **)&m_pIGraphBuilder);
然后创建一个
Capture Graph Builder
组件,利用这个组件上的
ICaptureGraphBuilder2
接口来完成
Filter Graph
的构建。
hr=CoCreateInstance(CLSID_CaptureGraphBuilder2,NULL,CLSCTX_INPROC,
IID_ICaptureGraphBuilder2,(void **)&m_pICaptureGraphBuilder2);
接着通过调用接口方法
ICaptureGraphBuilder2::SetFilterGraph(m_pIGraphBuilder)
将
Filter Graph Manager
对象指针设置给
Capture Graph Builder
组件来完成对它的初始化。
m_pICaptureGraphBuilder2->SetFiltergraph(m_pIGraphBuilder);
3.1.2
加入采集
Filter
加入采集
Filter
是通过系统枚举的方法来实现的。即在
DirectShow
的
GraphEdit
目录
Video Capture Sources(
对应的
ID
为:
CLSID_VideoInputDeviceCategory)
下找到相应的采集设备即可。
核心代码如下:
hr=CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,(LPVOID *)&pDevEnum);//
创建系统枚举组件对象
hr=pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEnum,0);
//
指定枚举的类型目录,获得
IEnumMoniker
接口
if (hr == S_OK)
{
IMoniker *pMoniker=NULL;
if(pEnum->Next(1,&pMoniker,NULL)==S_OK)//
假设系统中只有一个采集设备,故枚
//
举到的第一个设备就是符合我们要求的采集设备
{
hr=pMoniker->BindToObject(0,0,IID_IBaseFilter,(void **)&m_pCapture);
//
创建采集
Filter
实例
}
枚举到采集设备后,下一步的工作就是把它加入到
Filter Graph
中去。代码如下:
hr=m_pIGraphBuilder->AddFilter(m_pCapture,L"CaptureFilter");
3.1.3
加入
MPEG-4
编码器
Filter
这里我们采用
Divx
提供的开源编码
Filter
。安装
DivX.Pro.v5.1.1
后会自动安装
Divx
的编码器
Filter
和解码器
Filter
(注:解码器
Filter
在接收端要用到)。在程序中加入
Divx
的编码器
Filter
,实现思想是在
Video Compressors
目录下枚举到名称为
"DivX Pro(tm) 5.1.1 Codec"
的
Filter
项后,把它加入到
Filter Graph
中即可。
3.2
数据的发送和接收
3.2.1
数据的发送
Filter
的实现
数据的发送要开发一个发送
Filter
,为了编程上的方便,这里采用程序内
Filter
的形式来实现。即用类的形式而不是编写一个成一个后缀为
ax
的组件注册后再使用。这里我们定义一个继承自
CBaseFilter
的类
CFilterMpeg4Sender
。这个类必须实现以下功能
[3]
:
(1)
在类中定义
CFilterMpeg4Sender
上的
Pin
的实例
mInputPin
。
(2)
实现继承自
CBaseFilter::GetPin,
用于返回
Filter
上各个
Pin
的对象指针。
(3)
实现继承自
CBaseFilter::GetPin,
用于返回
Filter
上各个
Pin
的数量。
定义一个继承自
CRenderedInputPin
的类
CMpeg4InputPin
,用于实现
CFilterMpeg4Sender
上的输入
pin
,发送
Filter
通过该输入
pin
接收编码
Filter
输出的数据,然后按一定的规则发送。
这个类必须实现以下功能
[2]
:
(1)
重写方法
EndOfStream
。
(2)
实现
IPin::BeginFlush
和
IPin::EndFlush
两个函数。
(3)
重写方法
CBasePin::CheckMediaType
进行连接时媒体类型的检查。
(4)
重写方法
CBasePin:: Receive(),
接收
Sample
并发送
3.2.2
数据的接收
Filter
的实现
数据的接收其实是要编写一个
Source Filter,
这个
Source Filter
名称为
CFilterMpeg4
Receiver
,也继承自
CBaseFilter
。这跟发送
Filter
的实现有些类似,有一点需要注意的是该
Filter
输出的
MediaType
的设置。
Char MediaType[]=//
媒体数据类型
,
通过在发送端把媒体类型写到一个文件中而得到
然后通过语句:
CFilterMpeg4
Receiver::SetupMediaType((char *)MediaType,88)
设置输出数据的
MediaType
。
CFilterMpeg4
Receiver::SetupMediaType
再调用
CMpeg4OutPin::SetupMediaType
()设置、接收到的媒体数据的格式,
3.2.3
数据的网络传输的实现
数据的发送我们采用开源代码
JRTPLIB
【
6
】
提供的
RTP
协议栈。最新的
JRTPLIB
对
RFC3550
的实现进行了封装,开发人员只要初步了解
RTP
协议就可以开发出高质量的音视频传输程序。使用
JRTPLIB
时,只需要通过继承
RTPSession
类,再重新以下几个函数就可以实现视频数据的接收。
void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const
RTPAddress *senderaddress)
;
void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime
&receivetime, const RTPAddress *senderaddress)
;
在网络带宽比较低的情况下(如十几
KBps
),数据丢帧现象比较严重,这对于图像质量有很大的影响。我们采用拆帧(拆成
1400
个字节)以后再发送的方法,来降低丢帧率。接收端收到数据后,再把属于同一视频帧的数据再组起来。
网络发送接收程序流程图如图
3
所示:
图3 网络发送接收程序流程图
对程序流程图的说明如下:
(
1
)发送端拆帧的算法如下:
if (
该数据帧小于
1400
个字节
)
{
直接用
RTPSessio
::
Send()
发送出去。
}
else
{
把该帧拆成
1400
个字节一个包再发送。对于同一帧数据,我们采用相同的时间戳来标记。
}
(
2
)接收端组帧算法如下:
while(
该
RTP
包的时间戳和上一个
RTP
包的时间戳相同
)
{
说明该
RTP
包和上一个
RTP
包属于同一个视频帧的数据。
把接收到的数据保存在缓存中。
}
然后把属于同一视频帧的数据组好,发送到解码
Filter
。
经过测试(在
CDMA1.X
网络下),采用拆帧方法传输视频数据比直接发送丢包率更低,传输质量有了很大的提高。
3.3
数据解码及回放的实现
解码
Filter
使用的是
Divx
提供的开源解码器,在接收
Filter
的后面接上该解码
Filter
即可,最后接上
Renderer Filter
就可以把接收到的数据回放出来。
3.4
实现帧率控制功能
通过在采集设备和编码
Filter
(
DivX Pro(tm) 5.1.1 Codec
)之间加入一个帧率控制
Filter
来实现帧率的控制,该
Filter
相当于一个视频帧数计数器,每接收到一帧,并不立即把该帧发给下游的编码
Filter
,而是把计数器的值加
1
,当计数器的值达到最大值时才把当前收到的帧发出去。在接收端发控制帧率命令给采集端可以很方便的实现帧率的远端控制。
程序片断如下:
HRESULT CControlFrameFilter::Receive(IMediaSample * pSample)
{
static int sampleCounter=1;
if(sampleCounter==maxCounter)// maxCounter
为计数器的最大值
{
OutputPin()->Deliver(pSample);//
把该帧发到下游编码
Filter
sampleCounter=0;//
计数器清零
}
sampleCounter++;//
计数器加
1
return S_OK;
}}
加了帧率控制
Filter
的发送端
Filter Graph
如图
4
所示:
图4
实现了帧率控制的Filter Graph
4
总结
该系统采用了
DirectShow
技术实现了
MPEG-4
视频数据的传输,视频数据的传输采用了
RTP
协议。而且还实现了远端帧率的控制,该系统可以很方便的移植到未来
3G
网络的图像传输系统中。对编解码器进行研究,采用
H.264
技术实现编解码
Filter
是下一步要完成的工作,当然在传输质量(
QoS
)方面也要深入进行研究。
参考文献
1
邵林
,
曹汉强
.
基于
DiectShow
的视频广播系统设计与实现
[J].
微型机与应用
,2004, 4 :58-60
2 Microsoft DirectX C++ SDK Document [EB/OL],2003
3
陆其明
.
DiectShow
开发指南
[M].
北京
.
清华大学出版社
,2004
4
陆其明
.
DiectShow
实务精选
[M].
北京
:
科学出版社
,2004
5
张明华
.
《基于
RTP
的视频传输控制方法的研究》
[D].
郑州市:郑州大学
, 2004.3
6 Jori Liesenborgs JRTPLIB 3.1.0
[EB/OL]
http://research.edm.luc.ac.be/jori/jrtplib/jrtplib.pdf