• 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如何写文件。

  • 相关阅读:
    自定义Spark Partitioner提升es-hadoop Bulk效率
    golang channel本质——共享内存
    STM 软件事务内存——本质是为提高并发,通过事务来管理内存的读写访问以避免锁的使用
    elasticsearch 自定义_id
    JS弄ASP.NET(C#)在页GridView信息选择行
    pdf转换为word小工具,挺好
    Cocos2d-x场景功能描述的生命周期
    数据收集程序一般建筑(C++ ACE达到)
    IOS上传文件开发
    thinkphp达到UploadFile.class.php图片上传功能
  • 原文地址:https://www.cnblogs.com/94cool/p/1493511.html
Copyright © 2020-2023  润新知