Directshow開發的一些基本概念和技巧
1 視訊播放(Video Rendering)
dshow的視訊提交過濾器可以在視窗模式和無視窗模式下工作。在視窗模式下,過濾器建立一個自己的視窗,在裡面播放視訊。在無視窗模式/下,過濾器直接將視訊在應用程式提供的視窗上顯示,過濾器本身不建立視窗。
視窗模式
在視窗模式下,視訊提交過濾器建立一個視窗,然後將視訊禎帖到視窗上,你可以將這個視窗帖到你的應用程式的視窗。
Video Renderer只支援視窗模式,VMR-7 and VMR-9預設的是視窗模式,也支援無視窗模式。
為了在你的應用程式中顯示視訊,你可以將視訊視窗設定成應用程式的子視窗。你可以通過
IVideoWindow *pVidWin = NULL;
pGraph->QueryInterface(IID_IVideoWindow, (void **)&g_pVidWin);
pVidWin->put_Owner((OAHWND)hwnd);
pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
RECT grc;
GetClientRect(hwnd, &grc);
pVidWin->SetWindowPosition(0, 0, grc.right, grc.bottom);
結束時一定要清理現場
pControl->Stop();
pVidWin->put_Visible(OAFALSE);
pVidWin->put_Owner(NULL);
無視窗模式
當採用無視窗的模式時,就沒有必要暴露IVideoWindow介面了。
為了能夠使用VMR的預設行為,在構建Graph圖之前必須要調整VMR。
1 建立一個過慮器圖表管理器,
2建立一個VMR,加入到graph中,
3 呼叫VMR的IVMRFilterConfig::SetRenderingMode方法設定VMRMode_Windowless標誌。
4呼叫IVMRWindowlessControl::SetVideoClippingWindow 給視訊指定一個顯示視窗。
然後呼叫IGraphBuilder::RenderFile或者其他的方法來建立其他的Graph。
下面的程式碼顯示瞭如何建立一個VMR,將其新增到Graph,如何設定無視窗模式
HRESULT InitWindowlessVMR(
HWND hwndApp, // Window to hold the video.
IGraphBuilder* pGraph, // Pointer to the Filter Graph Manager.
IVMRWindowlessControl** ppWc, // Receives a pointer to the VMR. )
{
if (!pGraph || !ppWc) return E_POINTER;
IBaseFilter* pVmr = NULL;
IVMRWindowlessControl* pWc = NULL;
// Create the VMR.
HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL,
CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr);
if (FAILED(hr))
{
return hr;
}
// Add the VMR to the filter graph.
hr = pGraph->AddFilter(pVmr, L"Video Mixing Renderer");
if (FAILED(hr))
{
pVmr->Release();
return hr;
}
// Set the rendering mode.
IVMRFilterConfig* pConfig;
hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig);
if (SUCCEEDED(hr))
{
hr = pConfig->SetRenderingMode(VMRMode_Windowless);
pConfig->Release();
}
if (SUCCEEDED(hr))
{
// Set the window.
hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc);
if( SUCCEEDED(hr))
{
hr = pWc->SetVideoClippingWindow(hwndApp);
if (SUCCEEDED(hr))
{
*ppWc = pWc; // Return this as an AddRef'd pointer.
}
else
{
// An error occurred, so release the interface.
pWc->Release();
}
}
}
pVmr->Release();
return hr;
}
你也可以呼叫下面的函式
IVMRWindowlessControl *pWc = NULL;
hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc);
if (SUCCEEDED(hr))
{
// Build the graph. For example:
pGraph->RenderFile(wszMyFileName, 0);
// Release the VMR interface when you are done.
pWc->Release();
}
下面看看如何設定視訊的位置
有兩個矩形需要考慮,一個是源矩形,一個是目的矩形。源矩形決定開始播放視訊的位置,目的矩形決定在視窗顯示視訊的區域。VMR將源矩形
按照目的矩形的大小進行擴充套件。
IVMRWindowlessControl::SetVideoPosition可以設定兩個矩形的大小,源矩形必須小於等於本地視訊大小。你可以通過
IVMRWindowlessControl::GetNativeVideoSize獲取本地的視訊區域大小。
// Find the native video size.
long lWidth, lHeight;
HRESULT hr = g_pWc->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL);
if (SUCCEEDED(hr))
{
RECT rcSrc, rcDest;
// Set the source rectangle.
SetRect(&rcSrc, 0, 0, lWidth/2, lHeight/2);
// Get the window client area.
GetClientRect(hwnd, &rcDest);
// Set the destination rectangle.
SetRect(&rcDest, 0, 0, rcDest.right/2, rcDest.bottom/2);
// Set the video position.
hr = g_pWc->SetVideoPosition(&rcSrc, &rcDest);
}
處理視窗訊息
因為VMR沒有自己的視窗,所以當視訊需要重畫或者改變的時候你要通知它。
1 當你接到一個WM_PAINT訊息,你就要呼叫IVMRWindowlessControl::RepaintVideo來重畫視訊
2 當你接到一個WM_DISPLAYCHANGE訊息,你就要呼叫IVMRWindowlessControl::DisplayModeChanged.
3 當你接到一個WM_SIZE訊息時,重新計算視訊的位置,然後呼叫SetVideoPostion。
下面的程式碼演示了WM_PAINT訊息的處理
void OnPaint(HWND hwnd)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rcClient;
GetClientRect(hwnd, &rcClient);
hdc = BeginPaint(hwnd, &ps);
if (g_pWc != NULL)
{
// Find the region where the application can paint by subtracting
// the video destination rectangle from the client area.
// (Assume that g_rcDest was calculated previously.)
HRGN rgnClient = CreateRectRgnIndirect(&rcClient);
HRGN rgnVideo = CreateRectRgnIndirect(&g_rcDest);
CombineRgn(rgnClient, rgnClient, rgnVideo, RGN_DIFF);
// Paint on window.
HBRUSH hbr = GetSysColorBrush(COLOR_BTNFACE);
FillRgn(hdc, rgnClient, hbr);
// Clean up.
DeleteObject(hbr);
DeleteObject(rgnClient);
DeleteObject(rgnVideo);
// Request the VMR to paint the video.
HRESULT hr = g_pWc->RepaintVideo(hwnd, hdc);
}
else // There is no video, so paint the whole client area.
{
FillRect(hdc, &rc2, (HBRUSH)(COLOR_BTNFACE + 1));
}
EndPaint(hwnd, &ps);
}
儘管我們要自己處理onpaint訊息,但是已經非常簡單了。
2 如何處理事件通知(Event Notification)
當一個Directshow的應用程式執行的時候,在 filter Graph內部就會發生各種各樣的事件,例如,一個filter也許發生資料流錯誤。Filter
通過給graph mangaer傳送事件通知來和graph通訊,這個事件通知包括一個事件碼和兩個事件引數。事件碼錶示發生事件的型別,兩個引數用
來傳遞資訊。
Filter傳送的這些事件,其中的一部分可以被Manager直接處理,不通知應用程式,但有一部分事件,Manager將事件放入到一個佇列中,等待
應用程式處理。這裡我們主要討論在應用程式中經常遇到的三種事件
EC_COMPLETE表明回放已經結束
EC_USERABORT表明使用者中斷了回放。使用者關閉視訊播放視窗時,視訊Render會發生這個事件
EC_ERRORABORT表明出現了一個錯誤。
應用程式可以通知filter graph manager,在某個指定的事件發生時,向指定的視窗發生一個指定的訊息。這樣應用程式就可以在訊息迴圈中
對發生的事件產生反應。
首先定義訊息,
#define WM_GRAPHNOTIFY WM_APP + 1
然後向filter graph manager請求IMediaEventEx介面,然後呼叫IMediaEventEx::SetNotifyWindow方法來設定訊息通知視窗
IMediaEventEx *g_pEvent = NULL;
g_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&g_pEvent);
g_pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
然後在WindowProc函式增加一個處理WM_GRAPHNOTIFY訊息的函式
case WM_GRAPHNOTIFY:
HandleGraphEvent();
break;
HandleGraphEvent()函式具體定義如下
void HandleGraphEvent()
{
// Disregard if we don't have an IMediaEventEx pointer.
if (g_pEvent == NULL)
{
return;
}
// Get all the events
long evCode;
LONG_PTR param1, param2;
HRESULT hr;
while (SUCCEEDED(g_pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0)))
{
g_pEvent->FreeEventParams(evCode, param1, param2);
switch (evCode)
{
case EC_COMPLETE: // Fall through.
case EC_USERABORT: // Fall through.
case EC_ERRORABORT:
CleanUp();
PostQuitMessage(0);
return;
}
}
}
在釋放IMediaEventEx指標前,要取消事件通知訊息,程式碼如下
// Disable event notification before releasing the graph.
g_pEvent->SetNotifyWindow(NULL, 0, 0);
g_pEvent->Release();
g_pEvent = NULL;
3如何列舉系統的裝置和過慮器
有時,應用程式需要檢視系統中所有的filter。例如,視訊應用程式需要列出系統中可用的捕捉裝置。因為dshow基於com結構的,你在設計
程式的時候是沒法知道系統中正在使用的過濾器。Directshow提供了兩種方法來列舉系統中註冊的過慮器。
1 系統裝置列舉器
系統裝置列舉器提供了一個很好的方法根據種類來列舉系統中註冊的過慮器。也許枚一種不同的硬體都會有自己的過慮器,或許所有的硬體設
備共用同一個filter。這個對於採用WDM驅動程式的硬體很有用。
系統裝置列舉器根據不同的種類建立了一個列舉器,例如,音訊壓縮,視訊捕捉。不同種類的列舉器對於每一種裝置返回一個獨立的名稱
(moniker)。種類列舉器自動將相關的即插即用,演播裝置包括進來。
按照下面的步驟使用裝置列舉器
1 建立列舉器元件,CLSID為CLSID_SystemDeviceEnum
2 指定某一種型別裝置,引數CLSID,通過ICreateDevEnum::CreateClassEnumerator獲取某一種類的列舉器,這個函式返回一個IEnumMoniker
介面指標,如果該種類的空或者不存在,這個方法就返回S_FALSE。因此,當你呼叫這個函式時一定要檢查返回值是否為S_OK,而不要用
SUCCEEDED巨集。
3 然後IEnumMoniker::Next列舉每一個moniker。這個方法返回一個IMoniker介面指標。
4 要想知道裝置的名稱,可以通過下面的函式IMoniker::BindToStorage
5 然後利用IMoniker::BindToObject生成繫結道裝置上的filter。呼叫IFilterGraph::AddFilter將filter新增到Graph圖中。
// Create the System Device Enumerator.
HRESULT hr;
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr))
{
return hr;
}
// Obtain a class enumerator for the video compressor category.
IEnumMoniker *pEnumCat = NULL;
hr=pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat, 0);
if (hr == S_OK)
{
// Enumerate the monikers.
IMoniker *pMoniker = NULL;
ULONG cFetched;
while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);//知道裝置的名稱
if (SUCCEEDED(hr))
{
// To retrieve the filter's friendly name, do the following:
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
// Display the name in your UI somehow.
}
VariantClear(&varName);
// To create an instance of the filter, do the following:
IBaseFilter *pFilter;
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter); //生成一個filter繫結到裝置上。
// Now add the filter to the graph.
//Remember to release pFilter later.
pPropBag->Release();
}
pMoniker->Release();
}
pEnumCat->Release();
}
pSysDevEnum->Release();
在上面我們用IMoniker::BindToObject生成繫結道裝置上的filter,當然我們還可以用另外的一種方法來生成繫結到裝置上的filter 利用
IMoniker::GetDisplayName得到moniker的名字。然後你把moniker的名字做引數傳遞給IFilterGraph2::AddSourceFilterForMoniker,就可以
建立一個繫結到裝置的filter了。在上面我們是呼叫IMoniker::BindToObject生成filter的,還是上面的簡單些。看看程式碼吧。
LPOLESTR strName = NULL;
IBaseFilter pSrc = NULL;
hr = pMoniker->GetDisplayName(NULL, NULL, &strName);
if (SUCCEEDED(hr))
{
// Query the Filter Graph Manager for IFilterGraph2.
IFilterGraph2 *pFG2 = NULL;
hr = pGraph->QueryInterface(IID_IFilterGraph2, (void**)&pFG2);
if (SUCCEEDED(hr))
{
hr = pFG2->AddSourceFilterForMoniker(pMoniker, 0, L"Source", &pSrc);
pFG2->Release();
}
CoTaskMemFree(strName);
}
// If successful, remember to release pSrc.
2 Filter Mapper
搜尋系統中的filter的另一個方法就是採用Filer Mapper。Filter mapper是一個com物件,它按照一定的條件來搜尋系統的filer,它比系統
裝置列舉器(System Device Enumerator)的效率要低一些。所以當你要列舉某特定種類的filter時,你應該使用系統裝置列舉器,但是當你
搜尋支援某種媒體型別的filter時,同時也找不到清晰的filter,你應該使用filter mapper。
Filter Mapper 暴露一個IFilerMapper2介面,要想搜尋一個介面,你可以呼叫該介面的IFilterMapper2::EnumMatchingFilters方法,這個方
法需要傳遞一些引數來定義搜尋條件,同時該方法返回一個適合條件的filter的列舉器,這個列舉器提供一個IEnumMoniker介面,並且對於每
個適合的filter都提供一個單獨的moniker。
下面的例子演示了,列舉所有的支援DV,並且至少有一個輸出pin的filter,這個filter支援任何媒體型別。
IFilterMapper2 *pMapper = NULL;
IEnumMoniker *pEnum = NULL;
hr =CoCreateInstance( CLSID_FilterMapper2,NULL, CLSCTX_INPROC, IID_IFilterMapper2, (void **) &pMapper);
if (FAILED(hr))
{
// Error handling omitted for clarity.
}
GUID arrayInTypes[2];
arrayInTypes[0] = MEDIATYPE_Video;
arrayInTypes[1] = MEDIASUBTYPE_dvsd;
hr = pMapper->EnumMatchingFilters(&pEnum,
0, // Reserved.
TRUE, // Use exact match?
MERIT_DO_NOT_USE+1, // Minimum merit.
TRUE, // At least one input pin?
1, // Number of major type/subtype pairs for input.
arrayInTypes, // Array of major type/subtype pairs for input.
NULL, // Input medium.
NULL, // Input pin category.
FALSE, // Must be a renderer?
TRUE, // At least one output pin?
0, // Number of major type/subtype pairs for output.
NULL, // Array of major type/subtype pairs for output.
NULL, // Output medium.
NULL); // Output pin category.
// Enumerate the monikers.
IMoniker *pMoniker;
ULONG cFetched;
//////////下面就是列舉filter了,就是系統列舉裝置filter
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag = NULL;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
if (SUCCEEDED(hr))
{
// To retrieve the friendly name of the filter, do the following:
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
// Display the name in your UI somehow.
}
VariantClear(&varName);
// To create an instance of the filter, do the following:
IBaseFilter *pFilter;
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);
// Now add the filter to the graph. Remember to release pFilter later.
// Clean up.
pPropBag->Release();
}
pMoniker->Release();
}
// Clean up.
pMapper->Release();
pEnum->Release();
4如何列舉Graph圖中的物件(filter,pin)
有些時候,應用程式需要列舉graph中的filter或者是列舉filter所支援的pin。因此directshow提供了列舉graph filter中的com元件方法。
1 列舉filter
Filter圖表管理器支援IFilterGraph::EnumFilters方法,來列舉graph圖中的所有的filter。他返回一個IEnumFilters介面,利用這個介面就
可以遍歷graph中的所有的filter。
下面的程式碼演示了,如何遍歷graph中的filter,並且顯示filter的名字。
HRESULT EnumFilters (IFilterGraph *pGraph)
{
IEnumFilters *pEnum = NULL;
IBaseFilter *pFilter;
ULONG cFetched;
HRESULT hr = pGraph->EnumFilters(&pEnum);
if (FAILED(hr)) return hr;
while(pEnum->Next(1, &pFilter, &cFetched) == S_OK)
{
FILTER_INFO FilterInfo;
hr = pFilter->QueryFilterInfo(&FilterInfo);
if (FAILED(hr))
{
MessageBox(NULL, TEXT("Could not get the filter info"),
TEXT("Error"), MB_OK | MB_ICONERROR);
continue; // Maybe the next one will work.
}
#ifdef UNICODE
MessageBox(NULL, FilterInfo.achName, TEXT("Filter Name"), MB_OK);
#else
char szName[MAX_FILTER_NAME];
int cch = WideCharToMultiByte(CP_ACP, 0, FilterInfo.achName,
MAX_FILTER_NAME, szName, MAX_FILTER_NAME, 0, 0);
if (chh > 0)
MessageBox(NULL, szName, TEXT("Filter Name"), MB_OK);
#endif
// The FILTER_INFO structure holds a pointer to the Filter Graph
// Manager, with a reference count that must be released.
if (FilterInfo.pGraph != NULL)
{
FilterInfo.pGraph->Release();
}
pFilter->Release();
}
pEnum->Release();
return S_OK;
}
2 列舉pin
Filter支援IBaseFilter::EnumPins方法,這個方法可以可以列舉filter所有的pin。它返回一個IEnumPins介面,IEnumPins::Next可以遍歷pin
的介面。
下面的程式碼演示瞭如何如何查詢一個輸出和輸入pin。利用PIN_DIRECTION引數來制定pin的型別(輸入還是輸出)。
HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin)
{
IEnumPins *pEnum = NULL;
IPin *pPin = NULL;
HRESULT hr;
if (ppPin == NULL)
{
return E_POINTER;
}
hr = pFilter->EnumPins(&pEnum);
if (FAILED(hr))
{
return hr;
}
while(pEnum->Next(1, &pPin, 0) == S_OK)
{
PIN_DIRECTION PinDirThis;
hr = pPin->QueryDirection(&PinDirThis);
if (FAILED(hr))
{
pPin->Release();
pEnum->Release();
return hr;
}
if (PinDir == PinDirThis) //如果型別符合
{
// Found a match. Return the IPin pointer to the caller.
**ppPin = pPin;
pEnum->Release();
return S_OK;
}
// Release the pin for the next time through the loop.
pPin->Release();
}
// No more pins. We did not find a match.
pEnum->Release();
return E_FAIL;
}
利用這個方法可以很容易的就查詢一個pin,然後呼叫IPin::ConnectedTo方法確定這個pin是否被連線,可以查詢一個空閒的pin。
3 查詢媒體型別
每個pin都支援一個IPin::EnumMediaTypes方法,可以來列舉pin支援的媒體型別。它返回一個IEnumMediaTypes介面,這個介面的方法
IEnumMediaTypes::Next返回一個指向AM_MEDIA_TYPE型別的指標。可以參考上面的程式碼來遍歷pin所支援的媒體型別。
5 Seeking Filter graph
主要講述瞭如何在一個媒體資料流中定位,任意指定開始播放的位置。
1 檢查是否支援seek
Directshow通過IMediaSeeking介面支援seeking。Filter graph管理器支援這個介面,但是實際seeking的功能是有graph中的filter來實現的。
有一些資料是不能seek的,例如,你不可能seek從照相機中採集的活動的視訊流。如果一個數據流可以被seek,但是,seek的型別還分以下幾
種類型,可以給你的資料流選擇一種
1 定位到資料流中的一個絕對位置
2 返回資料流的持續時間
3返回資料流中的當前播放位置
4回放。
IMediaSeeking介面定義了一套標誌AM_SEEKING_SEEKING_CAPABILITIES,用來描述可能支援的seek功能。
typedef enum AM_SEEKING_SeekingCapabilities {
AM_SEEKING_CanSeekAbsolute = 0x1,
AM_SEEKING_CanSeekForwards = 0x2,
AM_SEEKING_CanSeekBackwards = 0x4,
AM_SEEKING_CanGetCurrentPos = 0x8,
AM_SEEKING_CanGetStopPos = 0x10,
AM_SEEKING_CanGetDuration = 0x20,
AM_SEEKING_CanPlayBackwards = 0x40,
AM_SEEKING_CanDoSegments = 0x80,
AM_SEEKING_Source = 0x100
} AM_SEEKING_SEEKING_CAPABILITIES;
可以通過IMediaSeeking::GetCapabilities檢視資料流支援的seek能力都有哪些。應用程式可以採取 &測試每一項。例如,下面的程式碼檢查了
graph是否可以seek 一個任意的位置
DWORD dwCap = 0;
HRESULT hr = pSeek->GetCapabilities(&dwCap);
if (AM_SEEKING_CanSeekAbsolute & dwCap)
{
// Graph can seek to absolute positions.
}
2 Setting and Retrieving the Position
Filter graph包含兩個位置,當前位置和停止位置,定義如下:
1 當前位置,當一個graph正處於執行的時候,當前位置就是當前的回放位置,相對於開始的位置而言。如果graph處於停止或者暫停狀態的時候,當前位置就是資料流下次開始播放的位置點。
2 停止位置,停止位置就是資料流將要停止的位置,當一個graph到達一個停止位置時,將沒有資料流,filter graph管理器將會發送一個
EC_COMPLETE事件。
可以通過IMediaSeeking::GetPositions方法可以獲取這些位置值。返回值都是相對於原始的開始位置。
通過IMediaSeeking::SetPositions方法可以seek一個新的位置,見下面:
#define ONE_SECOND 10000000
REFERENCE_TIME rtNow = 2 * ONE_SECOND,
rtStop = 5 * ONE_SECOND;
hr = pSeek->SetPositions( &rtNow, AM_SEEKING_AbsolutePositioning, &rtStop, AM_SEEKING_AbsolutePositioning );
注:1秒是10,000,000參考時間單位。為了方便,這個例子將這個值定義為ONE_SECOND,如果你使用的dshow的基類,常量CUITS的值和這個值相等。
RtNow引數指定新的當前位置,第二個引數用來標示如何來定位rtNow引數。在這個例子中,AM_SEEKING_AbsolutePositioning 標誌表示rtNow
指定的位置是一個絕對的位置。RtStop引數指定了停止時間,最後一個引數也指定了絕對位置。
如果想指定一個相對的位置,可以指定一個AM_SEEKING_RelativePositioning引數,為了設定這個位置不能改變,可以指定一個AM_SEEKING_NoPositioning引數。此時,參考時間應該設定為NULL。下面的例子將位置向前seek 10秒,然後停止位置不變。
REFERENCE_TIME rtNow = 10 * ONE_SECOND;
hr = pSeek->SetPositions( &rtNow, AM_SEEKING_RelativePositioning, NULL, AM_SEEKING_NoPositioning );
3 Setting the Playback Rate
呼叫IMediaSeeking::SetRate方法可以改變回放的速率。通過將新的速率設定成原來速率的倍數就可以設定新的速率,例如,pSeek->SetRate(2.0)
將新的速率設定為原來速率的兩倍。比率大於1說明回放的速度比原來的大,如果介於0和1之間,就比正常的速度慢。
如果我們不考慮回放速率,當前位置和停止位置相對於開始位置都是不變的。舉個例子,如果我們有一個可以播放20秒的檔案,將當前時間設定為10秒就會將播放位置設定到中間,如果播放的速率提高要原來的2倍,如果停止時間是20秒,你將播放位置設定到原來的10秒處,結果現在只能播放5秒了,因為速度提高了兩倍。
4 Time Formats For Seek Commands
IMediaSeeking介面中的許多函式的引數都要求指定一個位置值,比如當前位置,或者停止位置,預設的情況下這些引數是以of 100 nanoseconds為時間單位的,稱為參考時間,任何支援seek的filter必須支援按參考時間來進行定位。一些filter也支援採取其他時間單位進行定位。例如,根據指定的楨的數量,或在資料流偏移的位元組數進行定位。
這種用來定位的時間單位稱為時間格式,採用一個GUID來標示。Directshow定義了一系列的時間格式,詳細地可以參考SDK。第三方也可以定義自己的時間格式。
為了確定graph中的當前的filter是否支援特定的時間格式,可以呼叫IMediaSeeking::IsFormatSupported方法,如果filter支援該時間格式,該函式返回ok否則返回false或者一個錯誤碼。如果filter支援某種指定的時間格式,可以呼叫IMediaSeeking::SetTimeFormat方法切換到其他的時間格式。如果SetTimeFormat方法成功,下面的seek命令就要使用新的時間格式。
下面的程式碼檢查graph是否支援用楨的數量進行定位,如果支援,定位到第20楨。
hr = pSeek->IsFormatSupported(&TIME_FORMAT_FRAME);
if (hr == S_OK)
{
hr = pSeek->SetTimeFormat(&TIME_FORMAT_FRAME);
if (SUCCEEDED(hr))
{
// Seek to frame number 20.
LONGLONG rtNow = 20;
hr = pSeek->SetPositions( &rtNow, AM_SEEKING_AbsolutePositioning, 0, AM_SEEKING_NoPositioning);
}
}
6 如何設定Graph時鐘(Setting Graph Clock)
當你構建了一個graph後,graph管理器會自動地給你的graph選擇一個參考時鐘的。Graph中的所有filter都同步於時鍾。特別的,Renderer filter還要根據參考時鐘的時間來決定每一個sample的Presentation 時間。
通常的情況下,應用程式是沒有必要重新設定graph管理器選擇好的參考時鐘的。但是,如果你想修改參考時鐘,你可以通過graph管理器提供的IMediaFilter::SetSyncSource方法來重新設定參考時鐘。這個方法的引數是一個時鐘的IReferenceClock介面指標。可以在graph停止的時候呼叫這個函式,下面的例子演示瞭如何指定一個時鐘
IGraphBuilder *pGraph = 0;
IReferenceClock *pClock = 0;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
// Build the graph.
pGraph->RenderFile(L"C://Example.avi", 0);
// Create your clock.
hr = CreateMyPrivateClock(&pClock);
if (SUCCEEDED(hr))
{
// Set the graph clock.
IMediaFilter *pMediaFilter = 0;
pGraph->QueryInterface(IID_IMediaFilter, (void**)&pMediaFilter);
pMediaFilter->SetSyncSource(pClock);
pClock->Release();
pMediaFilter->Release();
}
這段程式碼假定CreateMyPrivateClock 是應用程式定義的一個函式,用來建立一個時鐘,然後返回一個IReferenceClock介面。
你也可以在graph沒有設定時鐘的情況下執行graph。當SetSyncSource 函式的引數為NULL的時候就給graph設定了一個空的參考時鐘。如果graph沒有時鐘,graph將執行的快許多。因為renderer 不用再按照sample的presentation 時間了,只要sample到達了renderer filter,就可以立即被提交。所以,當你想處理資料儘可能快,而不是還要考慮預覽的實際時間,你就可以給graph設定一個空的時間。