分类: WINDOWS
2007-01-23 14:51:13
DES(DirectShow Editing Services)是一套基于DiretShow核心技术框架的编程接口。它的出现简化了视频编缉任务,弥补了DirectShow对媒体文件非线性编辑支持的先天性不足。
Timeline:组织各个媒体源、音视频效果、过渡效果等的信息集合,实际上代表了最终的视频剪辑作品。
XML Parser:将Timeline结构转化为XML格式文件进行保存,或者从XML文件生成对应的Timeline。
Render Engine:将Timeline实际转化为DShow Filter实现输出控制引擎。
Media Locator:用于定位媒体文件。
在DES中,Timeline被用来表示一个视频剪辑工程(Video Editing Project),暂且称之为视频剪辑过程。剪辑的过程,如果没有媒体源是不行的。而在DES中,媒体源可以是音视频文件,甚至可以是图片。一个个媒体源(Source)线性的连接就构成了一个Track。需要注意的是,我们在使用DES建立Timeline的时候,音频和视频要处在不同的Track之上。
多个音频和视频Tracks的处理,我们可以向其中加入Effects(效果),以及视频之间的Transitions(过渡效果)。DES提供了100多种SMPTE过渡效果(Transitions),同时我们还可以使用IE自带的各种音视频效果(Effect),或者Transitions(过渡)。(如果想知道具体有什么过渡效果,可以运行DXSDK ROOT\Samples\C++\DirectShow\Bin\TransViewer.exe查看。
从上面的架构图中可以看出,多个Track(可以是一个)构成Composition,而多个Composition(同样可以是一个)构成Group,而Group(s)将构成Timeline。如果剪辑过程包括音视频,那么该Timeline至少有2个Group(Video Group & Audio Group)。
上面是SDK里面一个示例Timeline的时间抽象图。对于在时间上有重叠的情况,DES将采取以下方法:
1. 对于Video Group,Track1的优先级高于Track0,如果发上重叠现象,Track1上面的图像将被显示。
2. 对于Audio Group, DES将采用混合(Mixing)的方式进行处理。
在建立Timeline的时候,我们涉及两个时间概念:
1. 时间线时间(Timeline Time):相对于整个时间线项目的时间。
2. 媒体时间(Media Time):相对于媒体源的时间,就是指相对于媒体文件开头的时间。
我们就拿SDK的例子来讲解如何创建Timeline。该例子位于DXSDK ROOT\Samples\C++\DirectShow\Editing\TimelineTest。
首先我们会注意到在TimelineTest.cpp的开头一个预处理宏:
#001 #ifdef STRICT
#002 #undef STRICT
#003 #endif
让我们来了解一下STRICT宏。在MSDN里面,STRICT宏作用是用来进行严格的类型检测。Windows.h头文件定义了一系列的宏,结构等用来使编译出来的代码可以在不同的Windows版本之间运行,当我们在定义了STRICT的环境下编译时,四种数据的类型将改变(下面只列举了一种情况,其他的可以参见MSDN):
1. 当我们在没有定义STRICT的时候,Windows的所有的HANDLE都被视为interger,意思就是说如果我们将一个HWND传递给HDC的时候,这种情况将是被允许的。反之,如果定了STRICT,编译器将报错。因为此时编译器进行了严格的类型检测。
让我们重新回到该示例程序。该示例程序Timeline的建立过程就在函数TimelineTest里面。
变量的申明部分:
#001 CComPtr< IRenderEngine > pRenderEngine;
#002 CComPtr< IGraphBuilder > pGraph;
#003 CComPtr< IVideoWindow > pVidWindow;
#004 CComPtr< IMediaEvent > pEvent;
#005 CComPtr< IAMTimeline > pTimeline;
#006 CComPtr< IAMTimelineObj > pVideoGroupObj;
#007 CComPtr< IAMTimelineObj > pAudioGroupObj;
#008 CComPtr< IMediaControl > pControl;
#009 CComPtr< IMediaSeeking > pSeeking;
所有的变量什么都采用了ATL Libraries里面的CComPtr(该宏的作用有点像智能指针,在使用的时候不用担心资源的释放问题)。第一个变量的申明IRenderEngine接口,该接口的作用是通过建立好的Timeline来建立Filter Graph供以后的预览或者输出文件。
那我们应该如何创建Timeline呢?首先我们来回忆一下COM变量的创建过程。创建一个COM实体,我们可以通过CoCreateInstance和QueryInterface。那么两个有什么不同呢。CoCreateInstance通过特定的CLSID创建一个没有初始化的实体(对象)。而QueryInterface的调用是为了获取调用对象的接口以提供另外的操作。
所以我们首先应该创建一个Timeline对象(所有的异常处理都被省略):
#001 hr = CoCreateInstance(
#002 CLSID_AMTimeline,
#003 NULL,
#004 CLSCTX_INPROC_SERVER,
#005 IID_IAMTimeline,
#006 (void**) &pTimeline
#007 );
接下来我们将创建Video Group。
#001 hr = pTimeline->CreateEmptyNode( &pVideoGroupObj, TIMELINE_MAJOR_TYPE_GROUP );
#002
#003 CComQIPtr< IAMTimelineGroup, &IID_IAMTimelineGroup > pVideoGroup( pVideoGroupObj );
#004
#005 CMediaType VideoGroupType;
#006
#007 // all we set is the major type. The group will automatically
#008 // use other defaults
#009 VideoGroupType.SetType( &MEDIATYPE_Video );
#010 hr = pVideoGroup->SetMediaType( &VideoGroupType );
我们通过IAMTimeline::CreateEmptyNode创建IAMTimelineObj接口,此时我们获得了该接口的一个指针。IAMTimeline::CreateEmptyNode的第二个参数传递的要创建object的枚举类型,该枚举类型的定义如下:
#001 typedef enum {
#002 TIMELINE_MAJOR_TYPE_COMPOSITE = 1,
#003 TIMELINE_MAJOR_TYPE_TRACK = 2,
#004 TIMELINE_MAJOR_TYPE_SOURCE = 4,
#005 TIMELINE_MAJOR_TYPE_TRANSITION = 8,
#006 TIMELINE_MAJOR_TYPE_EFFECT = 16,
#007 TIMELINE_MAJOR_TYPE_GROUP = 128
#008 } TIMELINE_MAJOR_TYPE;
值得注意对是,每个DES对象都是实现了IAMTimelineObj接口,而且各个具体的对象实现了各自特殊的接口,参考如下:
1. Source: IAMTimelineSrc, IAMTimelineEffectable, IAMTimelineSplittable;
2. Track: IAMTimelineTrack, IAMTimelineVirtualTrack,, IAMTimelineEffectable, IAMTimelineTransable, AMTimelineSplittable;
3. Composition: IAMTimelineComp, IAMTimelineVirtualTrack, IAMTimelineEffectable, IAMTimelineTransable;
4. Group: IAMTimelineGroup, IAMTimelineComp;
5. Effects: IAMTimelineEffect, IAMTimelineSplittable;
6. Transitions: IAMTimelineTrans, IAMTimelineSplittable;
创建了还不行,我们必须将Group加入到Timeline中:
#001 hr = pTimeline->AddGroup( pVideoGroupObj );
同样的过程我们创建一个Track,并且把它加入到Timeline中:
#001 CComPtr< IAMTimelineObj > pTrack1Obj;
#002 hr = pTimeline->CreateEmptyNode( &pTrack1Obj, TIMELINE_MAJOR_TYPE_TRACK );
#003
#004 //--------------------------------------------
#005 // tell the composition about the track
#006 //--------------------------------------------
#007
#008 CComQIPtr< IAMTimelineComp, &IID_IAMTimelineComp > pRootComp( pVideoGroupObj );
#009 hr = pRootComp->VTrackInsBefore( pTrack1Obj, -1 );
加入的过程我们调用的VTrackInsBefore,该函数的第二个参数是插入的track在composition中优先级。如果要将该Track插入到优先级的最后(意思就是默认的插入),使用参数-1。
加入Source,并且设置Source在Timeline的时间和媒体源的时间(Media Time):
#001 CComPtr
#002 hr = pTimeline->CreateEmptyNode( &pSource1Obj, TIMELINE_MAJOR_TYPE_SOURCE );
#003
#004 // set up source right
#005 //
#006 hr = pSource1Obj->SetStartStop( TLStart, TLStop );
#007 CComQIPtr< IAMTimelineSrc, &IID_IAMTimelineSrc > pSource1Src( pSource1Obj );
#008
#009 hr |= pSource1Src->SetMediaTimes( MediaStart, MediaStop );
#010 hr |= pSource1Src->SetMediaName( pClipname );
上面的过程已经建立好Timeline,并且建立好了Source、Track和Composition。如果没有另外的Effects和Transitions的加入,我们就可以建立Filter Graph然后预览。不过我们还是来介绍如何加入Transition吧。
建立Transitions的过程(其实包括所有的IAMTimelineObj的创建过程)实际上是调用IAMTimeline的接口CreateEmtpyNode(他们传递的类型参数不同而已)。
#001 CComPtr
#002 hr = pTimeline->CreateEmptyNode(&pTrackTransObj,
#003 TIMELINE_MAJOR_TYPE_TRANSITION );
然后我们设置采用的Transitions(这里我们用DXT_Jpeg)和过渡的执行时间。
#001 REFERENCE_TIME TransStart = 0 * UNITS;
#002 REFERENCE_TIME TransStop = 4 * UNITS;
#003
#004 // we set the CLSID of the DXT to use instead of a
#005 // pointer to the actual object. We let the DXT
#006 // have it's default properties.
#007 //
#008 hr = pTrackTransObj->SetSubObjectGUID( CLSID_DxtJpeg );
#009 hr |= pTrackTransObj->SetStartStop( TransStart, TransStop );
#010
#011 CComQIPtr< IAMTimelineTrans, &IID_IAMTimelineTrans > pTrackTrans( pTrackTransObj );
#012 hr |= pTransable->TransAdd( pTrackTransObj );
不过现在我们还不能就此结束,对于DXT_Jpeg的过渡,其过渡方式有207个,如何决定采用那个呢,MSDN上面有详细的介绍。当我们选择好了过渡方式之后,又该如何来设置它呢,不用慌,下面给出了示例代码。
#001 CComPtr< IPropertySetter > pTransSetter;
#002 hr = CoCreateInstance( CLSID_PropertySetter, NULL, CLSCTX_INPROC_SERVER,
#003 IID_IPropertySetter, (void**) &pTransSetter );
#004
#005 DEXTER_PARAM Param;
#006 CComBSTR ParamName( "MaskNum" ); // the property name
#007 Param.Name = ParamName;
#008 Param.nValues = 1; // how many values we want to set
#009
#010 DEXTER_VALUE Value;
#011 memset( &Value, 0, sizeof( Value ) );
#012 VariantClear( &Value.v );
#013 V_I4( &Value.v ) = 128; // mask number 128
#014 V_VT( &Value.v ) = VT_I4; // integer
#015
#016 hr = pTransSetter->AddProp( Param, &Value );
#017 hr |= pTrackTransObj->SetPropertySetter( pTransSetter );
音频的处理跟视频差不多,这里就不介绍了。如果需要,打开SDK里面该源代码,他会向你讲解的。现在就差不多了J,接下来我们进行预览。
整个的过程我都省去了异常处理,不过这儿我还是得介绍一下ValidateSourceNames,该函数检查Source的有效性,当我们在预览或者输出文件之间,进行文件源的有效性是有必要的(我曾经陷入过死循环中)L。
#001 //----------------------------------------------
#002 // make sure files are in their correct location
#003 //----------------------------------------------
#004
#005 hr = pTimeline->ValidateSourceNames(
#006 SFN_VALIDATEF_CHECK | SFN_VALIDATEF_POPUP | SFN_VALIDATEF_REPLACE,
#007 NULL,
#008 0 );
#009 ASSERT( !FAILED( hr ) );
说了这么久,IRenderEngine也该出场了吧。让我们来建立IRenderEngine:
#001 hr = CoCreateInstance(
#002 CLSID_RenderEngine,
#003 NULL,
#004 CLSCTX_INPROC_SERVER,
#005 IID_IRenderEngine,
#006 (void**) &pRenderEngine );
回想一下IRenderEngine的作用,该接口的作用是通过建立好的Timeline来建立Filter Graph供以后的预览或者输出文件。所以我们要把Timeline的信息传递给它。
#001 // tell the render engine about the timeline it should look at
#002 //
#003 hr = pRenderEngine->SetTimelineObject( pTimeline );
接下来的过程很简单了。运用ConnectFrontEnd连接Timeline部分所建立的Filter,RenderOutputPins来预览(自动建立Renderer和连接Filter进行预览)。
#001 //--------------------------------------------
#002 // connect up the front end, then the back end
#003 //--------------------------------------------
#004
#005 hr = pRenderEngine->ConnectFrontEnd( );
#006 hr |= pRenderEngine->RenderOutputPins( );
最后的过程就是运行Graph了。
#001 hr = pRenderEngine->GetFilterGraph( &pGraph );
#002 hr |= pGraph->QueryInterface( IID_IMediaEvent,
#003 (void**) &pEvent );
#004 hr |= pGraph->QueryInterface( IID_IMediaControl,
#005 (void**) &pControl );
#006 hr |= pGraph->QueryInterface( IID_IMediaSeeking,
#007 (void**) &pSeeking );
#008 hr |= pGraph->QueryInterface( IID_IVideoWindow,
#009 (void**) &pVidWindow );
#010
#011 long lStyle=0;
#012 hr = pVidWindow->get_WindowStyle(&lStyle);
#013
#014 lStyle &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
#015 hr = pVidWindow->put_WindowStyle(lStyle);
#016
#017 //--------------------------------------------
#018 // run it
#019 //--------------------------------------------
#020
#021 hr = pControl->Run( );