DES的建立过程

DES(DirectShow Editing Services)是一套基于DiretShow核心技术框架的编程接口。它的出现简化了视频编缉任务,弥补了DirectShow对媒体文件非线性编辑支持的先天性不足。

DES的构架

Timeline:组织各个媒体源、音视频效果、过渡效果等的信息集合,实际上代表了最终的视频剪辑作品。

XML Parser:将Timeline结构转化为XML格式文件进行保存,或者从XML文件生成对应的Timeline。

Render Engine:将Timeline实际转化为DShow Filter实现输出控制引擎。

Media Locator:用于定位媒体文件。

基于Timeline的DES模型

在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查看。

/////

技术原理
  DES (DirectShow Editing Services),是一套基于DirectShow核心框架的编程接口。DES的出现,简化了视频编辑任务,弥补了DirectShow对于媒体文件非线性编辑支持的先天性不足。但是,就技术本身而言,DES并没有超越DirectShow Filter架构,而只是DirectShow Filter的一种增强应用。我们可以从下图中了解到DES在我们整个多媒体处理应用中的位置。


 
  下面,我们举个例子来看一下DES能够给我们带来些什么。假如我们现在有三个文件A、B和C,使用这三个文件做成一个合成的文件。我们想取A的4秒钟的内容,紧接着取B的10秒钟的内容,再紧接着C的5秒钟的内容。如果仅仅是这样,我们直接使用DirectShow Filter是不难实现的。(一般情况下,应用程序级会维持各个文件的编辑信息,由应用程序根据这些信息动态创建/控制功能单一的Filter Graph,以顺序对各个文件进行处理。)但是,如果我们的"创意"是随时改变的,我们现在想让C在B之前出现,或者我们想取A的不同位置的10秒钟内容,或者我们想给整个合成的文件加上一段美妙的背景音乐。如果我们仍然直接使用DirectShow Filter去实现,情况就变得很复杂了。然而,对于DES,这真的是小Case!(将所有的编辑信息以DES提供的接口告诉DES,其它的如Filter Graph的创建/控制输出,就完全交给DES来负责吧!这时候,DES创建的Filter Graph带有各个Source输出的控制功能,一般比较复杂。)

  如果我们使用DES,我们还可以得到如下的便利:

  1. 基于时间线(Timeline)的结构以及Track的概念,使得多媒体文件的组织、编辑变得直观而高效;
  2. 支持即时的预览;
  3. 视频编辑项目支持XML文档的形式保存;
  4. 支持对视频/音频的效果处理,以及视频之间切换的过渡处理;
  5. 可以直接使用DES提供的100多种SMPTE过渡效果,以及MS IE自带的各种Transform、Transition组件;
  6. 支持通过色调、亮度、RGB值或者alpha值进行图像的合成;
  7. 自动对源文件输出的视频帧率、音频的采样率进行调整,直接支持视频的缩放。

  接下去,我们来看一下DES的结构(Timeline模型),如下图所示:

  这是一个树形结构。在这棵树中,音视频文件是叶结点,称作为Source;一个或多个Source组成一个Track,每个Track都有统一的媒体格式输出;Track的集合称作为Composition,每个Composition可以对其所有的Composition或Track进行各种复杂的编辑;顶级的Composition或Track就组成了Group;每个Group输出单一格式的媒体流,所有的Group组成一个Timeline, Timeline表示一个视频编辑的项目,它是这棵树的根节点。一个Timeline项目必须至少包含一个Group,最典型的情况一般包含两个Group:Audio Group和Video Group。
 下面,我们来看一个典型的基于Timeline的Source Track编排。如下图:

  图中,箭头方向即是Timeline的方向。这个Timeline由两个Group组成,每个Group中包含两个Source Track。在Group中,Track是有优先级的(Track 0具有最低的优先级,依次类推)。运行时,总是输出高优先级的Track中的Source内容。如果此时高优先级的Track中没有Source输出,则让低优先级的Track中的Source输出。如上图中Video Group的输出顺序为Source A->Source C->Source B。而对于Audio Group,它的所有Track的输出只是简单的合成。
  我们再看一个典型的Track之间加入了Transition的Timeline结构。如下图:

  图中,Video Group中是两个Track以及Track上几个Source的编排;Rendered video中表示这个Group最终输出的效果。我们可以看到,在Track 1上有一个Transition,表示这个时间段上从Track 0过渡到Track1的效果。一般,Transition位于高优先级的Track上。Transition也是有方向的,默认是从低优先级的Track过渡到高优先级的Track。当然,我们也可以改变Transition的方向。如下图所示,第一个Transition是从Track 0到Track 1,第二个Transition是从Track 1到Track 0。

 

  值得注意的是,DES使用的Transition采用了叫做DirectX Transform Object的技术。任何两输入一输出的DirectX Transform Object都可以用作Transition。遗憾的是,微软现在的DirectX SDK不再支持这种组件的开发。我们能够使用的,只有DES本身提供的几种效果,还有就是Microsoft Internet Explorer自带的效果。DES使用的Effect情况类似,只不过DES Effect是单输入单输出的DirectX Transform Object。


//////


从上面的架构图中可以看出,多个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):相对于媒体源的时间,就是指相对于媒体文件开头的时间。

使用Timeline
创建Timeline
我们就拿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<IAMTimelineObj> pSource1Obj;

#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
建立Transitions的过程(其实包括所有的IAMTimelineObj的创建过程)实际上是调用IAMTimeline的接口CreateEmtpyNode(他们传递的类型参数不同而已)。

#001 CComPtr<IAMTimelineObj> pTrackTransObj;

#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,接下来我们进行预览。

预览Timeline
整个的过程我都省去了异常处理,不过这儿我还是得介绍一下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( );

到此结束。写文件部分请看DES如何写文件。

原文地址:https://www.cnblogs.com/94cool/p/1493511.html