摘要
本文詳細闡述了基於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感興趣的同仁,互相學習和交流。
下載源代碼:http://www.vckbase.com/code/downcode.asp?id=2681