Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2716651
  • 博文数量: 416
  • 博客积分: 10220
  • 博客等级: 上将
  • 技术积分: 4193
  • 用 户 组: 普通用户
  • 注册时间: 2006-12-15 09:47
文章分类

全部博文(416)

文章存档

2022年(1)

2021年(1)

2020年(1)

2019年(5)

2018年(7)

2017年(6)

2016年(7)

2015年(11)

2014年(1)

2012年(5)

2011年(7)

2010年(35)

2009年(64)

2008年(48)

2007年(177)

2006年(40)

我的朋友

分类: WINDOWS

2007-01-23 14:51:13

什么是DES

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

DES的构架

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

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

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

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

基于TimelineDES模型

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至少有2GroupVideo Group & Audio Group)。

上面是SDK里面一个示例Timeline的时间抽象图。对于在时间上有重叠的情况,DES将采取以下方法:

1.  对于Video GroupTrack1的优先级高于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实体,我们可以通过CoCreateInstanceQueryInterface。那么两个有什么不同呢。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,该函数的第二个参数是插入的trackcomposition中优先级。如果要将该Track插入到优先级的最后(意思就是默认的插入),使用参数-1

加入Source,并且设置SourceTimeline的时间和媒体源的时间(Media Time):

#001 CComPtr 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,并且建立好了SourceTrackComposition。如果没有另外的EffectsTransitions的加入,我们就可以建立Filter Graph然后预览。不过我们还是来介绍如何加入Transition吧。

建立Transitions

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

#001 CComPtr 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部分所建立的FilterRenderOutputPins来预览(自动建立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如何写文件。J
posted on 2005-03-16 22:02 番茄程序缘
阅读(1709) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~