分类: WINDOWS
2007-01-23 09:22:59
基于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感兴趣的同仁,互相学习和交流。