本文詳細闡述基於 DirectShow 核心框架的非線性編輯服務(DES)原理,並提供範例程式碼演示如何拼接兩個音視頻檔案、實現視頻轉場效果與預覽。
DirectShow Filter 架構雖強大,但對非線性編輯支援先天不足。若需動態調整剪輯順序、加入背景音樂、套用轉場特效,直接操作 Filter Graph 會極其複雜。DES 作為 DirectShow Filter 的增強應用層,提供高階編輯介面:開發者僅需以 DES 介面描述編輯決策(時間線、軌道、來源、轉場),其餘 Graph 建構與控制全權交由 DES 處理。
Timeline (根節點) ├── Group (輸出單一媒體流,典型為 Audio Group、Video Group) │ ├── Composition (可包裝多 Track 為 Virtual Track) │ │ └── Track (優先級遞增,高優先級輸出優先) │ │ └── Source (葉節點,對應音視頻檔案片段)
關鍵規則:Group 中 Track 有優先級,運行時輸出高優先級 Track 的內容;無內容時回落至低優先級。Audio Group 所有 Track 內容混合輸出。Transition 置於高優先級 Track,預設從低優先級向高優先級過渡,方向可改變。
DES 使用 DirectX Transform Object 實現 Transition(雙輸入單輸出)與 Effect(單輸入單輸出)。可惜 Microsoft 已停止支援此類元件開發,僅能使用 DES 內建效果與 IE 自帶元件。Composition 採用 Composite 設計模式,可將多 Track 封裝為 Virtual Track,支援更複雜的巢狀編輯。
編譯環境:Windows XP + VC6.0 SP5 + DX9 SDK。需連結 strmiids.lib。
void CDesTestDlg::OnStart() {
CoInitialize(NULL);
IAMTimeline *pTL = NULL;
CoCreateInstance(CLSID_AMTimeline, NULL, CLSCTX_INPROC_SERVER,
IID_IAMTimeline, (void**)&pTL);
// 建立 Video Group
IAMTimelineGroup *pGroup = NULL;
IAMTimelineObj *pGroupObj = NULL;
pTL->CreateEmptyNode(&pGroupObj, TIMELINE_MAJOR_TYPE_GROUP);
pGroupObj->QueryInterface(IID_IAMTimelineGroup, (void**)&pGroup);
AM_MEDIA_TYPE mtGroup = {0};
mtGroup.majortype = MEDIATYPE_Video;
pGroup->SetMediaType(&mtGroup);
pTL->AddGroup(pGroupObj);
pGroupObj->Release();
// 加入兩個 Track
IAMTimelineTrack *pTrack1, *pTrack2;
IAMTimelineObj *pTrackObj1, *pTrackObj2;
IAMTimelineComp *pComp1 = 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
IAMTimelineSrc *pSource1, *pSource2;
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);
pSourceObj1->SetStartStop(0, 100000000);
pSourceObj2->SetStartStop(50000000, 100000000);
BSTR bstr1 = SysAllocString(OLESTR("news.WMV"));
BSTR bstr2 = SysAllocString(OLESTR("vos.wmv"));
pSource1->SetMediaName(bstr1);
pSource2->SetMediaName(bstr2);
SysFreeString(bstr1); SysFreeString(bstr2);
pSource1->SetMediaTimes(0, 100000000);
pSource2->SetMediaTimes(50000000, 100000000);
pTrack1->SrcAdd(pSourceObj1);
pTrack2->SrcAdd(pSourceObj2);
// Release sources...
// 建立 Transition (SMPTE Wipe - 六角星)
IAMTimelineObj *pTransObj = NULL;
pTL->CreateEmptyNode(&pTransObj, TIMELINE_MAJOR_TYPE_TRANSITION);
pTransObj->SetSubObjectGUID(CLSID_DxtJpeg);
pTransObj->SetStartStop(50000000, 100000000);
IAMTimelineTransable *pTransable = NULL;
pTrack2->QueryInterface(IID_IAMTimelineTransable, (void**)&pTransable);
pTransable->TransAdd(pTransObj);
// 設定 Transition 參數
IPropertySetter *pProp = NULL;
CoCreateInstance(CLSID_PropertySetter, NULL, CLSCTX_INPROC_SERVER,
IID_IPropertySetter, (void**)&pProp);
DEXTER_PARAM param; param.Name = SysAllocString(L"MaskNum");
param.dispID = 0; param.nValues = 1;
DEXTER_VALUE *pVal = (DEXTER_VALUE*)CoTaskMemAlloc(sizeof(DEXTER_VALUE));
pVal->v.vt = VT_BSTR; pVal->v.bstrVal = SysAllocString(L"129"); // 六角星
pVal->rt = 0; pVal->dwInterp = DEXTERF_JUMP;
pProp->AddProp(param, pVal);
SysFreeString(param.Name); VariantClear(&pVal->v); CoTaskMemFree(pVal);
pTransObj->SetPropertySetter(pProp);
pProp->Release(); pTransable->Release(); pTransObj->Release();
// 預覽
IRenderEngine *pRender = NULL;
CoCreateInstance(CLSID_RenderEngine, NULL, CLSCTX_INPROC_SERVER,
IID_IRenderEngine, (void**)&pRender);
PreviewTL(pTL, pRender);
pRender->ScrapIt(); pRender->Release(); pTL->Release();
CoUninitialize();
}
void PreviewTL(IAMTimeline *pTL, IRenderEngine *pRender) {
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
pRender->SetTimelineObject(pTL);
pRender->ConnectFrontEnd();
pRender->RenderOutputPins();
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();
pEvent->Release(); pControl->Release(); pGraph->Release();
}
DES 以時間線模型極大簡化了非線性編輯開發,讓開發者專注於編輯邏輯而非 Filter Graph 細節。本文範例展示了核心 API 使用流程,可作為進一步開發視頻編輯應用的基礎。