Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9728262
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-23 21:59:19

基于DirectShow非线性编辑DES

作者:



关键词 Timeline,Virtual Track,Transition,Audio Group和Video Group

摘要 
  本文详细阐述了基于DirectShow核心框架的非线性编辑的基本原理,并提供了一个编辑的源代码,演示如何拼接两个音视频文件,实现视频过渡效果,并预览。

编译环境 WindowsXP,VC6.0 sp5,DX9 SDK.

技术原理
  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。
  讲到这里,我们已经对DES结构有了一个初步的了解。我们需要回过去再看一看这个Timeline树结构。我们会发现,Group下面一般都有一个Composition,而随后的图例中,我们看到一般Group下直接嵌入的是Track。那么,Composition有什么用呢?熟悉《设计模式》的人很容易就明白了,微软采用的就是对象结构型模式的其中一种叫Composite(组合)的模式。Composition可以包装几个Track(这几个Track之间可能是包含Transition的),组成一个Virtual Track,并且与其他普通的Track接口保持一致。我们完全可以把这个Virtual Track与普通的Track一样操作,进而很方便地进行更加复杂、丰富的效果编辑
我提供了一个把两个.wmv文件进行编辑的源代码,有两个Track,为了简便,只是提供了视频的Transition,音频的Transition也是同样的道理,只不过需要多建一个Audio Group。程序编译需要安装DX9 SDK。目前上载系统有问题,无法上传源代码,以后补上。以下只主要源代码。
链接需要strmiids.lib库;


void CDesTestDlg::OnStart() 

{

	// 创建空时间线.



    IAMTimeline    *pTL = NULL;

    CoInitialize(NULL);

    CoCreateInstance(CLSID_AMTimeline, NULL, CLSCTX_INPROC_SERVER, IID_IAMTimeline, (void**)&pTL);



    // GROUP: Add a video group to the timeline. 



    IAMTimelineGroup    *pGroup = NULL;

    IAMTimelineObj      *pGroupObj = NULL;

    pTL->CreateEmptyNode(&pGroupObj, TIMELINE_MAJOR_TYPE_GROUP);

    pGroupObj->QueryInterface(IID_IAMTimelineGroup, (void **)&pGroup);



    // Set the group media type. This example sets the type to "video" and

    // lets DES pick the default settings. For a more detailed example,

    // see "Setting the Group Media Type."

    AM_MEDIA_TYPE mtGroup;  

    ZeroMemory(&mtGroup, sizeof(AM_MEDIA_TYPE));

    mtGroup.majortype = MEDIATYPE_Video;

    pGroup->SetMediaType(&mtGroup);

    pTL->AddGroup(pGroupObj);

    pGroupObj->Release();



    // TRACK: Add two track to the group. 



    IAMTimelineObj      *pTrackObj1,*pTrackObj2;

    IAMTimelineTrack    *pTrack1,*pTrack2;

    IAMTimelineComp     *pComp1 = NULL;//,*pComp2 = NULL;



    pTL->CreateEmptyNode(&pTrackObj1, TIMELINE_MAJOR_TYPE_TRACK);

    pGroup->QueryInterface(IID_IAMTimelineComp, (void **)&pComp1);

    pComp1->VTrackInsBefore(pTrackObj1, -1);

    pTrackObj1->QueryInterface(IID_IAMTimelineTrack, (void **)&pTrack1);



	pTL->CreateEmptyNode(&pTrackObj2, TIMELINE_MAJOR_TYPE_TRACK);

	pGroup->QueryInterface(IID_IAMTimelineComp, (void **)&pComp1);

    pComp1->VTrackInsBefore(pTrackObj2, -1);

    pTrackObj2->QueryInterface(IID_IAMTimelineTrack, (void **)&pTrack2);



    pTrackObj1->Release();

	pTrackObj2->Release();

    pComp1->Release();   

    pGroup->Release();





    // SOURCE: Add two source to the track.



    IAMTimelineSrc *pSource1 = NULL,*pSource2 = NULL;

    IAMTimelineObj *pSourceObj1,*pSourceObj2;

    pTL->CreateEmptyNode(&pSourceObj1, TIMELINE_MAJOR_TYPE_SOURCE);

    pSourceObj1->QueryInterface(IID_IAMTimelineSrc, (void **)&pSource1);

	pTL->CreateEmptyNode(&pSourceObj2, TIMELINE_MAJOR_TYPE_SOURCE);

    pSourceObj2->QueryInterface(IID_IAMTimelineSrc, (void **)&pSource2);



    // Set the times and the file name.

    pSourceObj1->SetStartStop(0, 100000000);

	pSourceObj2->SetStartStop(50000000, 100000000);

    BSTR bstrFile1 = SysAllocString(OLESTR("news.WMV"));

	BSTR bstrFile2 = SysAllocString(OLESTR("vos.wmv"));

    pSource1->SetMediaName(bstrFile1); 

	pSource2->SetMediaName(bstrFile2); 

    SysFreeString(bstrFile1);

	SysFreeString(bstrFile2);

	//设置基于媒体本身的开始和结束时间

    pSource1->SetMediaTimes(00000000, 100000000);

	pSource2->SetMediaTimes(50000000, 100000000);

    pTrack1->SrcAdd(pSourceObj1);

	pTrack2->SrcAdd(pSourceObj2);



    pSourceObj1->Release();

	pSourceObj2->Release();

    pSource1->Release();

	pSource2->Release();

    pTrack1->Release();

	pTrack2->Release();

	

	// Create the transition object. 

	IAMTimelineObj *pTransObj = NULL;

	HRESULT hr = pTL->CreateEmptyNode(&pTransObj, TIMELINE_MAJOR_TYPE_TRANSITION);

	

	// Set the subobject. 

	hr = pTransObj->SetSubObjectGUID(CLSID_DxtJpeg);  // SMPTE Wipe

	

	// Set the start and stop times. 

	hr = pTransObj->SetStartStop(50000000, 100000000);

	

	// Insert the transition object into the timeline. 

	IAMTimelineTransable *pTransable = NULL;

	hr = pTrack2->QueryInterface(IID_IAMTimelineTransable, (void **)&pTransable);

	hr = pTransable->TransAdd(pTransObj); 

	

	IPropertySetter     *pProp;   // Property setter

	

	hr = CoCreateInstance(CLSID_PropertySetter, NULL, CLSCTX_INPROC_SERVER,

		IID_IPropertySetter, (void**) &pProp);

	

	// Error checking is omitted for clarity...

	

	DEXTER_PARAM param;

	DEXTER_VALUE *pValue = (DEXTER_VALUE*)CoTaskMemAlloc(sizeof(DEXTER_VALUE));

	

	// Initialize the parameter. 

	param.Name = SysAllocString(L"MaskNum");

	param.dispID = 0;

	param.nValues = 1;

	

	

	// Initialize the value.

	pValue->v.vt = VT_BSTR;

	pValue->v.bstrVal =SysAllocString(L"129"); //六角星

	pValue->rt = 0;

	pValue->dwInterp = DEXTERF_JUMP;

	

	pProp->AddProp(param, pValue);

	

	// Free allocated resources.

	SysFreeString(param.Name);

	VariantClear(&(pValue->v));

	CoTaskMemFree(pValue);

	

	// Set the property on the transition.

	pTransObj->SetPropertySetter(pProp);

	pProp->Release();

	

	pTransable->Release();

	pTransObj->Release();

	

    // Preview the timeline.

    IRenderEngine *pRenderEngine = NULL;

    CoCreateInstance(CLSID_RenderEngine, NULL, CLSCTX_INPROC_SERVER,

        IID_IRenderEngine, (void**) &pRenderEngine);

    PreviewTL(pTL, pRenderEngine);

	

    // Clean up.

    pRenderEngine->ScrapIt();

    pRenderEngine->Release();

    pTL->Release();

    CoUninitialize();		

}



// 预览时间线.

void PreviewTL(IAMTimeline *pTL, IRenderEngine *pRender) 

{

    IGraphBuilder   *pGraph = NULL;

    IMediaControl   *pControl = NULL;

    IMediaEvent     *pEvent = NULL;



    // Build the graph.

    pRender->SetTimelineObject(pTL);

    pRender->ConnectFrontEnd( );

    pRender->RenderOutputPins( );



    // Run the graph.

    pRender->GetFilterGraph(&pGraph);

    pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);

    pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);

    pControl->Run();



    long evCode;

    pEvent->WaitForCompletion(INFINITE, &evCode);

    pControl->Stop();



    // Clean up.

    pEvent->Release();

    pControl->Release();

    pGraph->Release();

}		
最后,希望认识一些对MPEG-4感兴趣的同仁,互相学习和交流。
My MSN:freepublic@hotmail.com

阅读(296) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~