DirectShow是微软公司在ActiveMovie和Video for Windows的基础上推出的新一代基于COM(Component Object Model)的流媒体处理的开发包,9.0之前与DirectX开发包一起发布,之后包含在windows SDK中。。DirectShow使用一种叫Filter Graph的模型来管理整个数据流的处理过程,运用DirectShow,我们可以很方便地从支持WDM驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。这样使在多媒体数据库管理系统(MDBMS)中多媒体数据的存取变得更加方便。它广泛地支持各种媒体格式,包括Asf、Mpeg、Avi、Dv、Mp3、Wave等,为多媒体流的捕捉和回放提供了强有力的支持。另外,DirectShow还集成了DirectX其它部分(比如DirectDraw、DirectSound)的技术,直接支持DVD的播放,视频的非线性编辑,以及与数字摄像机的数据交换。
ActiveMovie,开发代号 Quartz, 这个由 Geraint Davies 为微软公司设计的 DirectShow 的前身,在 Windows 3.0 时代,是作为一种对当时最流行的媒体平台 QuickTime 的回应而开发的。ActiveMovie 最早的出现是被附加在 Windows 95 上面的并且需要系统安装了 IE3.0 。它当时的使命是作为 IE 的附件播放在其窗口内的媒体文件,正如当时 QuickTime 为 Netscape 以及 IE 提供的服务那样,它的另一个功能是作为 Windows 视频技术(VFW,Video For Windows)的一个替换,特别地为在 VFW 架构中难于处理的 MPEG(移动图象专家组格式文件)文件提供辅助处理。
在 1998 年,大致在 DirectX 5 年代的时候,ActiveMovie 被重命名为 DirectShow(反映了微软公司在那时正在努力加强"直接地"在一个通常的取名系统之下与硬件合作的技术)并且被包含为 " DirectMedia SDK" 的一部份。在 DirectX 的 7 版中,DirectShow 变成了 DirectX SDK 主要组成部分而且如同 DirectInput 等其它 DirectX APIs 一样被给予了它自己的位置。甚至之后, DirectShow 被主要用来接收来自像一个手提摄像机这样的电视输入装置的数据,而且它从文件中显示数据的能力被广泛用在 Windows Media Player 上面。 从 2005 年四月起,DirectShow 被从 DirectX SDK 移除,必须单独下载Extra包才能得以支持,之后DirectShow的文档和示例被转移到Windows SDK,DirectShow也正式成为Windows的一个组件。然而,在编译某些 DirectShow 的示例时,DirectX SDK 仍然是必需的。
在网上找了一些资料,比较好资料的地址为 http://www.yesky.com/259/1854259.shtml ,大部分资料都来自于陆其明写的《DirectShow开发指南》和《DirectShow实务精选》两本书。总结如下:
1) DirectShow的系统结构
DirectShow的体系结构如图1所示。
DirectShow位于应用层中。它使用一种叫Filter Graph的模型来管理整个数据流的处理过程;参与数据处理的各个功能模块叫Filter;各个Filter 在Filter Graph中按一定的顺序连接成一条"流水线"协同工作。
( 可以看出TFilterGraph是个Filter的容器 )
按照功能来分,Filter大致分为三类:Source Filters、Transform Filters和Rendering Filters。
Source Filters主要负责取得数据,数据源可以是文件、因特网、或者计算机里的采集卡、数字摄像机等,然后将数据往下传输;
Transform Fitlers主要负责数据的格式转换、传输;
Rendering Filtes主要负责数据的最终去向,我们可以将数据送给声卡、显卡进行多媒体的演示,也可以输出到文件进行存储。
在DirectShow系统之上,我们看到的,即是我们的应用程序(Application)。应用程序要按照一定的意图建立起相应的Filter Graph,然后通过Filter Graph Manager来控制整个的数据处理过程。DirectShow能在Filter Graph运行的时候接收到各种事件,并通过消息的方式发送到我们的应用程序。这样,就实现了应用程序与DirectShow系统之间的交互。
2) Filter概述以及连接
过滤器(Filter)是DirectShow中最基本的概念。DirectShow是通过Filter Graph来管理Filter的。Filter Graph是Filter的"容器",而Filter是Filter Graph中的最小功能模块。
Filter是一种COM组件,对于每个Filter,都有其自己的Pin,它是由Filter创建的COM对象。 Filter通过Pin来进行他们之间的连接。
Pin分为两种:输出Pin和输入Pin。输出的Pin把Filter处理后的数据传送到Filter的外 部,而输入Pin则是把Filter外部的数据接收到Filter中,以便Filter对这些数据进行处理。
对于三种类型的Filter(Source Filter,Transform Filter,Rendering Filter)的连接图如下:
图2 Filter的连接
可以根据Filter所包含的输入Pin和输出Pin的熟练来判断Filter的类型:
只有输出Pin,没有输入pin,为Source Filter
既有输出Pin,又有输入pin,为Transform Filter
没有输出Pin,只有输入pin,为Rendering Filter
3)DirectShow 的重要接口
DirectShow采用了COM标准,所以很多重要的功能都是通过COM接口来完成。下面就列举一些重要的DirectShow的接口。
(1) IGraphBuilder接口
用于构造Filter Graph的接口,建立和管理一系列的Filter,过滤和处理源媒体流。
(2) IMediaControl接口
用于控制多媒体流在Filter Graph中的流动,如流的启动和停止。
(3) IMediaEvent接口
用于捕获播放过程中发生的事件,并通知应用程序,如EC_COMPLETE等。
(4) IVideoWindow接口
用于控制视频窗口的属性。
(5) IMeadiaSeeking接口
用于查找媒体的接口,定位流媒体,控制多媒体数据播放提供精确控制。
(6) IBaseFilter接口
从ImediaFilter接口继承,用来定义一个具体的过滤器指针,并对多媒体数据进行处理。
(7) IPin接口
用于管理两个过滤器之间的Pin,从而连接过滤器。
(8) IsampleGrabberCB接口
是Sample Grabber过滤器的一个接口,用于当流媒体数据通过过滤器时进行采样以获得帧图象。
还有一个可以参考的介绍,地址为:http://www.yesky.com/259/1854259.shtml
摘自:http://www.cnblogs.com/zhusd/archive/2010/06/18/1760139.html
1.DirectShow概述
DirectShow的主要功能是流媒体的采集与回放。
它集成了DirectDraw,DirectSound,Direct3D的一些技术。
它是一个开放的架构,你自己可以写自己的Filter组件。
DirectShow的架构:
2.Filter
Filter其实就是一个COM组件,但它有自己的特性。它由输入,处理,输出三部分组成,
输入,输出是叫PIN,输入的叫输入pin,输出的叫输出PIN。PIN也是一.种COM组件,它实现了IPin接口。
在Filter的实现里,PIN其实是Filter的一个数据成员。(个人想法)
(1)Filter分为三类:Source Filter(源过滤器),Transform Filter(转换过滤器) Render Filter(渲染过滤器)
(2)Source Filter 的主要互责音、视频数据的采集,读取。比如File Source Filter(文件源过滤器),它是从磁盘里的音视文件读取数据的。
Transform Filter 互责音、视数据的压缩,解压,编码、解码、分析。
比如:Spliiter Filter(分离器),其功能是将音频流和视频流分离开来
Mux Filter(复合器),将音频流和视频流 全成单一数据流
还有一此Encoder,Decoder,Compressor等。
(3)Render Filter 互责音、视流的渲染,就是和输出,它将数据流送到输出设备,(显示器、磁盘、声卡)
Filter有三种状态,运行,暂停,停止。
关于数据流:
3.Filter Graph(过滤器图)
Filter Graph是DirectShow一个基本的概念。我们开发一个DirectShow应用,其中最重要的步骤就是构造一个Filter Graph。下图就是一个
Filter Graph
这是一个实现本地文件(avi文件)回放的Filter Graph,File Source互责读文件理的数据,之后将数据传输给AVI Splitter,AVI Spliiter 将接收的数据进行分析,分离成视频流和音频流。视频流经过AVI Ddcompressor的解码,送到Video Renderer输出到显示器。从AVI Spliter出来的音频流则送到DirectSound Device,由DirectSound Device送到音频输出设备。
4.Filter Graph Manager
Filter Graph Manager也是一个COM组件,它是DirectShow的控制中心,它控制Filter的运行。它有以下几个功能:
(1)协调各个Filter的状态。Filter有运行,暂停,停止三种状态,在Filter Graph中,各个Filter的状态必须协调一到,否则会引起冲突。Filter Graph Manager互责协调各个Filter的状态。
(2) 提供一个参考时间,用以同步音频流和视频流。
(3) 提供一套用于建立Filter Graph的方法
(4)与应用程序进行交互,它会将Filter Graph内部发生的事件,通过消息的方式通知应用程序。
5.Sample
Filter Graph运行的时候,数据从上游的Filter传到下游的Filter,数据有一个单位,这个单位叫Sample,就是数据将分成一个一个的Sample进行传输。
6.Media Type
媒体类型是DirectShow的一个基本的概念,它描述了两个过滤器之间传输数据(Sample)的格式。两个过滤器在连接之前,必须就所采用的媒体类型进行协商。
媒体类型,用AM_MEDIA_TYPE结构体来表示,它的结构可以看MSDN文档。其中有几个域是比较重要的
typedef struct _MediaType{
GUID majortype //主类型
GUID subtype //辅助类型(进一步描述数据格式)
GUID formattype //更详细描述数据格式
ULONG cbFormat //描述formatype数据块的大小
BYTE* pbFormat //描述formattype数据块的内存指针
//其它略
}
比较常用的描述媒体类型的GUID
majortype的有:
MEDIATYPE_Video(数字视频)
MEDIATYPE_Audio(数字音频)
MEDIATYPE_AnologVideo(模拟视频) //似乎没用过
MEDIATYPE_AnologAudio(模拟音频) //同上
MEDIATYPE_Stream(字节流) //Filter Source Filter从文件里读出来的数据
MEDIATYPE_Interleaved //DV的数据流(包括音视频数据)
Subtype
//MEDIASUBTYPE_Y
Formattype
FORMAT_None
FORMAT_DvInfo (描述DV的数据类型格式)
FORMAT_VideoInfo (描述一般视频数据格式)
FORMAT_WaveFormatE (描述一般音频数据格式)
FORMAT_VideoInfo2 ////
媒体类型协商:两个过滤器在传输数据之前,必须就所采用的媒体类型进行协商。
7.Filter的连接过程
DirectShow提供了多方法来连接Filter,如IFilterGraph::ConectDirect,IGraphBuilder::Connect
Filter的连接过程实际是两个过滤器就媒体类型进行协商的过程。Filter连接的过程其实它们的PIN连接的过程,就是上游的输出PIN与上游的输入PIN连接。我们知道,每一个PIN都有它自己支持的媒体类型,而当两个PIN没有共同支持的媒体类型的时候,它们是不能连接的,所以连接之前要进行协商。
在Conect函数的实现里,可以看到大致过程是这样的:
连接是由输出PIN发出的
Connect Start
IF PIN已连接 THEN EXIT
IF Filter不是停止状态 THEN EXIT
//开始协商过程
CALL AgreeMediaType
Connect End
AgreeMediaType Start
在输出PIN或输入PIN媒体类型枚举器mediatypeEnums
调用函数TryMediaTypes,并把枚举器的指针传给它
AgreeMediaType End
TryMediaTypes Start
FOR EACH mediatype IN mediatypEnums
AttempConnection(media)
NEXT
TryMediaTypes End
AttemptConnection Start
CheckMediaType //检查媒体类型是否被支持(输出PIN自己)
IF 支持 THEN
在PIN上保存媒体类型
ELSE
FAILED
END IF
CALL ReceiveConnect // 询问输入PIN是否接受当前的媒体类型。
IF SUCCEED THEN
CALL CompleteConnect
ELSE
FAIL
END IF
AttemptConnect End
CompleteConnect的作用是完成连接后的一些后续工作,主要是决定数据传输的时候使用哪一个内存分配器,由谁来创建内存分配器(IMemAllocator)。
注:Filter传输的数据单元叫Sample,它是一个COM组件,封装了一个缓冲区。Sample由内存分配器来管理。内存分配器是实现了IMenAllocator接口的COM组件,互责管理Sample。
智能连接:构造Filter Graph的时候通过一些方法的调用,DirectShow会帮我们构造整个Filter Graph。用到的方法有IGraphBuilder::RenderFile, IGraphBuilder:Render
ICaptureGraphBuilder2::RenderStream
8.Filter 的数据传送
(1)数据传送有两种模式:拉模式,推模式。
推模式:上游Filter调用下游输入PIN上的IMemInputPin::Receive,使用这种模式的话,输入PIN 必须实现IMemInputPin接口。比较典型的使用推模式的Filter是Capture Source Filter。
拉模式:下游的Filter调用 上游输出PIN的IAsycnRender接口的方法,这是,输出PIN必须实现IAsyncReader接口。比较典型的使用拉模式的是Fillter Source.
(2)传送过程中的内存管理:在传送的过程中,必须分配一定的缓冲。这个过程是通过内存分配器(IMemAllocator)来实现,它管理一系列的Sample。Sample是一个COM对象(实现IMediaSample接口),它封装并管理一个缓冲区。
在连接的过程中,Filter协商双方共同使用的内存管理器,并知道了内存管理器的位置。
这样,当上游的Filter要传输数据的时候,它将调用IMemAllocator::GetBuffer来得到一个闲的Sample然后将数据进去,传输给下游。
9.关于Filter Graph的状态转换:
10.质量控制:好像没什么用,没用到过
11.音、视同步:
(1) 参考时钟:
主要就是为Filter Graph设置一个参考时钟,凡实现了IReferenceClock接口的都可以作为参考时钟,FGM(Filter Graph Manager)有一套选定参考时钟的策略:一般有Live Source(Capture Source Filter)就选它作时钟,因为,这种Filter 包装的硬件上一般都有一个定时器。如果没有Live Source,那就选Audio Renderer,因为声卡上一般也有定时器。如果还没有的话,那就看其它Filter有没有实现IReferenceClock。确实没有的话,就选系统时钟。
(2) 时间戳(Time Stample)
送到Renderer渲染的Sample必须打上时间戳,就是一个渲染的Start Time 和End Time
好让Renderer 为它选择渲染时间。IMediaSample::SetTime
12.DirectShow对硬件的支持
DirectShow中Filter是工作在用户模式(user mode),而硬件设备,比如:视频采集卡,是工作是内核模式(kernel mode),那么,它们是怎样进行交互的呢?DirectShow为我们提供了包装Filter,包装Filter也是工作用户模式,不过它可以跟硬件驱动程序进行交互,再通过硬件驱动去控制硬件设备。
DirectShow里面有一个叫ksProxy的包装Filter,它是支持所有WDM设备,它提供了一个框架,然后由硬件厂商进行实现。
二.常用的接口
比较有用的接口和方法
IBaseFilter 这是所有Filter至少要实现的接口
IPin PIN至少要实现的接口
IFilterGraph 提供了一些建立Filter Graph的方法
IGraphBuilder (这个接口是由IFilterGraph继承来的,它提供了一些更方便的方法来建立Filter Graph,比如:RenderFile,Render,AddSourceFilter)
IMediaControl(控制Filter Graph的状态)
IVideoWindow(控制视频窗口)
//接口方法:put_MessageDrain
IMediaEventEx(事件处理有关的)
IMediaSeeking(用于媒体播放定位的)
上面这些接口,都是通过Filter Graph Manager暴露出来的,我们只创建一个Filter Graph Manaer 组件,就可以它们
ICaptureGraphBuilder2(用来建立Capture Filter Graph,这是一个非常有用的接口)
方法:
(1)SetFiltergraph //设置对应的IGraphBuilder*,也就是FilterGraph
(2)RenderStream //用得最多的方法,对Capture Source Filter指定的输出PIN(Capture ,Preview,CC,VBI,)完成Filter Graph下游其它部分的构造。
它的原型如下:HRESULT RenderStream( const GUID *pCategory, const GUID *pType, IUnknown *pSource, IBaseFilter *pIntermediate, IBaseFilter *pSink);它比较常见的用法如下:
为了对视频进行预览,我们只要如下调用:
RenderStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video,pCap,NULL,NULL);
为了对视频进行保存的:
RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,pCap,NULL,pMux);
pMux是一个Mux Splitter对输入的音、视频数据合并并进行编码,
(3) SetOutputFileName 这个方法是在对视频进行保存时调用的,可以设置保存文件路径,保存类型(如avi,asf),并会返回一个Mux Filter指针。
它的原型是:
HRESULT SetOutputFileName( const GUID *pType, LPCOLESTR lpwstrFile, //文件保存位置 IBaseFilter **ppf, //返回一个Mux Filter指针 IFileSinkFilter **pSink //这个指针用来更改文件保存路径,如果不需要可以置为NULL);pType 可能是一个subtype,当它是subtype时,只能下面两种取值:
MEDIASUBTYPE_Avi(保存为Avi格式),
MEDIASUBTYPE_Asf(保存为Asf),
因为这两种格式是windows可以识别的,所以它自动返回一个Mux Filter指针
如果要保存为其它格式,那么要将pType这个参数指定为一个Mux Filter或一个File Writer的GUID。
模拟电视卡可能用到的接口:
ICrossbar //输入端子选择
IAMTuner
IAMTVTuner //电视卡上TV Tuner的包装类,控制电视频道的接收与选择
常用方法:put_Channel //设置接收频道
三、DirectShow应用开发一般步骤
1. DirectShow开发环境的配置(VC 6.0)
一般包含的头文件是 dshow.h 或streams.h 有时还会用到其它头文件,比如:qedit.h
用到的静态连接库文件:strmiids.lib strmbasd.lib(strmbase.lib Release版本) quartz.lib
winmm.lib uuid.lib
路径配置:VC tools\option\
有配置的时候,最后把所有的库文件都包含上去,这样连接的时候就不会出现莫名其妙的错误了。
2. DirectShow应用开发的一般步骤
(1) 创建一个Filter Graph Manager组件
(2) 构造一个完整的Filter Graph
(3) 从 Filter Graph Manager组件得到各种接口,比如:IMediaControl,对程序进行控制
具体如下:
(1) 创建Filter Graph Manager组件的代码如下:
hr=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_FilterGraph, (void**)&pGraph)
相关接口的查询:
IMediaControl *pControl=NULL;
Hr=pGraph->QueryInterface(IID_IMediaControl,(void**)&pControl):
IVideoWindow* pVW=NULL;
Hr=pGraph->QueryInterface(IID_IVideoWindow,(void**)&pVW);
(2) 之后,创建Filter Graph
这是整个程序最关键的部分
创建Filter Graph 有下面一些方法
IFilterGraph::AddFilter IFilterGraph::ConnectDirect
IGraphBuilder::AddSourceFilter IGraphBuilder::Connect IGraphBuilder::RenderFile
IGraphBuilder::Render
ICaptureGraphBuilder2::RenderStream
一般采用智能连接比较方便,当智能连接不行的时候再自己手动连接
(3) 视频窗口的设置:如果要对视频进行预览的话,那就要设置视频窗口。视频窗口是由Renderer Filter 自动创建出来,我们编程的时候,只需要对它时进行设置就行了
IVideoWindow接口用来专门控制视频窗口,用以下常见的方法:
//put_Owner 为视频窗口指定父窗口,通过这个方法,我们可以将视频窗口嵌到我们自己的窗口,比如一个静态文本控件,
SetWindowPosition 设置视频窗口的位置,
//put_MessageDrain 这个是比较有用的。对视频窗口而言,我们是不能改写它的窗口处理过程,因为它是由DirectShow自动创建的。我们可以通过这个方法将视频窗口收到的消息挂接到一个我们自己的窗口,这样就实现了对视频窗口的消息处理了。
//put_Visible 将视频窗口显示出来
//SetNotifyWindow 当Filter Graph中发生一些事件时,会向应用程序发送一些消息,这个方法是指定处理这此消息的的窗口
//put_WindowStyle 设置Video Window的样式,比如:
long style=0;
get_WindowStyle(&style);
style&=~WS_CAPTION;
put_WindowStyle(style)
这样就可以去掉Video Window的标题栏了
///put_FullScreenMode 设置全屏模式
(4)启动Filter Graph
pGraph->Run();
3. 常用的Filter Graph 的构造
构造Filter Graph,可以GraphEdit这个工具来测试
(1) 本地文件播放
最方便的方法:IGraphBuilder::RenderFile(filename);
只要选择播放的文件,IGraphBuilder就智能地帮你构造出整个Filter Graph
(2) 视频输入设备采集视频的预览
ICaptureGraphBuilder2 * pGB=NULL;
Hr=CoCreateInstance (CLSID_CaptureGraphBuilder2,NULL,CLSCTX_INPROC,IID_ICaptureGraphBuilder2,(void**)&pGB);
//对于DV面言,它可能有两种视频输出PIN,一种是Interleaved Pin, 一种是Video Pin
//前面一种包含音视频流,后面一种只有视频流,所以我们预览的时候,可以先试一下
Interleaved Pin,如果失败的话,再试一下Video Pin
Hr=pGB->RenderStream(PIN_CATEGORY_PREVIEW,MEDIATYPE_Interleaved,
//pCap,NULL,NULL);
if(FAILED(hr))
{
hr=pGB->RenderStream(PIN_GATEGORY_PREVIEW,MEDIATYPE_Video,pCap,NULL,NULL);
}
if(FAILED(hr))
{
return hr;
}
(3) 采集视频的录制(输出为本地文件)
pGB->SetOutputFileName(&MEDIASUBTYPE_Asf, filename, &pMux,&pSink);
MEDIASUBTYPE_Asf 指定为保存为Asf格式,
MEDIASUBTYPE_Avi 保存为AVI格式
如是要保存为其它格式,要有相应格式的Mux Spliiter 或 Filter Writer,然后传入它的GUID
filename 是保存路径
hr=pGB->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Interleaved,pCap,NULL,pMux);
if(FAILED(hr))
{
hr=pGB->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,pCap,NULL,pMux);
}
(4) 模拟电视卡VBI信息的渲染
电视卡的VBI信息,用以下三种:CC(Closed Caption),WST(World Standard Teletext), 忘记了
CC( Closed Caption)
电视卡的VBI信号可能是从VBI PIN输出的,也可能是从CC PIN输出的,所以我们要进行判断。
Hr=pGB->RenderStream(&PIN_CATEGORY_VBI,&MEDIATYPE_Video,pCap,NULL,NULL);
If(FAILED(hr))
{
pGB->RenderStream(&PIN_CATEGORY_CC,&MEDIATYPE_Video,pCap,NULL,NULL);
}
if(FAILED(hr))
{
return hr;
}
RenderStream会帮我们创建整个Filter Graph
RenderStream不支持WST信号的渲染,我们必须手动创建Filter Graph
四.杂记
(1)电视卡Crossbar, TV Tuner
Crossbar是用于输入端子的选择。使用的时候,我们只要用ICrossbar::Route 导通相应的端子就可以了。有的电视卡有两个Crossbar。
TV Tuner控制电视卡接收与频道选择,可以通过接口IAMTVTuner进行操作
(2)Stream Buffer Engine
这个可以实现实时流媒体的缓冲。它有两个Filters,Stream Buffer Source,Stream Buffer Sink
Stream Buffer Source 互责流媒体的采集,并写入磁盘,以供给 Stream Buffer Sink读取。
对于流媒体,Stream Buffer Source只支持DV和MPEG-2两种格式,所以,在输入Source时,有时候要进行格式转换。Stream Buffer Source 优先采用的格式是MPEG-2。
Stream Buffer Sink 互责读取Source 写入的数据,然后进行回放。
读取的时候,它会先找到Stream Buffer Source写入的一个 Stub文件,里面包含了指向其它文件的指针。
使用Stream Buffer Engine时,我们要构造两个Filter Graph。
(3) WM Asf Writer
将视频保存为Asf文件的时候,要用到WM Asf Writer,它有一个音频输入端,一个视频输入端,它要求只两个端都要接上,否则构造出来的Filter Graph无法启动。这个要求感觉有点勉强,不知有没有解决方法。
(4) ISampleGrabber
在创建CLSID_SampleGrabber组件的时候,我们不能直接就得到里面的ISampleGrabber
,而是要先得到IBaseFilter接口,然后再用QueryInterface得到ISampleGrabber.感觉很奇怪。
根据IUnknown原则,无论我们先得到那个接口都是可以的啊。
摘自:http://hi.baidu.com/584433640/blog/item/7e682befa89ef927b90e2d53.html
还可参考:http://www.cnblogs.com/zhusd/category/250519.html
http://blog.csdn.net/langyifei/article/details/2474499