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

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-03-11 20:42:40

摘要 
  本文详细阐述了基于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
阅读(1681) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~