DirectShow的模型,基本上是這樣的。它提供一個容器物件,稱為Graph,在這個容器內,應用程式可以加入或移除任意多的Filter,還提供IMediaSeeking、IMediaControl及IMediaEventEx來達成整個串流的前進、後退、播放、停止、暫停、快轉及獲取進度資訊等。利用Graph及各種Builder,就可以管理這些Filter,並且將它們組織出合用的功能。要初始化一個Graph,利用以下敍述即可:
CComPtr
...
HRESULT hr=E_FAIL;
hr=m_gbuilder.CoCreateInstance(CLSID_FilterGraph); ASSERT(SUCCEEDED(hr));
GraphBuilder介面是由Graph物件所提供的最簡單的一個Builder。它提供智慧型連接功能(Intelligent Connect),並且分別實作在四個函式上:hr=m_gbuilder.CoCreateInstance(CLSID_FilterGraph); ASSERT(SUCCEEDED(hr));
- AddSourceFilter:自動添加相對應的Source Filter,其判斷基準為: (1)通訊協定的識別子(2)檔案的副檔名(3)檔案的二進位特徵。
- Connect:自動嚐試連接傳入的一對上下游Pin,若是無法連線,就使用 IFilterMapper2::EnumMatchingFilters列舉出相容於上游的Filter,再次進行匹配。
- Render:自動建構出一條源自傳入的Filter到Renderer的可行路徑。
- RenderFile:根據檔案格式,自動加入所需Filter,並建構出一條可行路徑到Renderer。
Builder的種類有好幾種,比如DVDGraphBuilder跟CaptureGraphBuilder,如果想要省事。就應該多多使用它們。
一般來說,Graph還要決定時鐘,時鐘是用來同步整個串流輸出的依據,一般會由Filter、應用程式或Graph自己提供。在沒有時鐘的情況下,Graph會以全速運作。如果今天的目標是要做個轉檔程式的話,就應該去除Graph的時鐘。
雖然隨著應用的不同,使用的Filter也不一樣,但總體來說,除了數位電視卡的應用以外,其它的Graph基本上都大同小異。因此,根據在Graph中的分工,Filter大致可以分成:
- Source: 又分為一般的Reader或是Live source,一般的Reader也常跟Splitter/Demuxer作在一起,可以讀入二進位檔案並剖析。若是Live source的話,它就是指從網路上或是硬體上將資料抓回來的Filter,常見的有ksproxy.ax(由ks.sys和廠商的miniport driver構成Kernel Stream,再交由ksproxy.ax扔出來,常見於Camera)、ASF Reader等等。
- Splitter/Demuxer:簡單講,就是用來剖析二進位資料的Filter,常常跟Source作在一起,它們有能力將容器檔案(如avi, mkv或rmvb)的內容分成數條軌道,比如說音軌、影像軌或字幕軌。一般都是翻譯成分歧器或是解多工器。
- Decoder:就是解碼器啦,影音資料常常是被壓縮過的,要靠它才能還原成一幀幀的點陣影像,如H.264、VC-1、AC3等等。另外也有專門針對藏在影像內的資訊作解碼的Decoder,像Line-21解碼器就是。另外,解碼器若支援DXVA的話,就可以將資料透過DirectX的DXVA API交由GPU解碼並且直接播放。
- Transformer:應用程式可以自行導入一些額外的Filter來達成一些特別功能,如SampleGrabber可以程式化,作到影像強化、編修等功能、或是格式上的轉換,如Color Space Converter可以轉換YUV空間到RGB空間等等。
- Renderer:把影像繪置在某種平面上。一般常見的平面當然是顯示卡了,但是我們也可以自行開發能把影像印到印表機上的Renderer,只是好像沒什麼意義就是了。Orz...對了,如果作業系統是WinXP的話,推薦使用VMR7,若是在Vista以上則推薦使用EVR。至於VMR9嘛,不是很理想。尤其是遇到Aero Glass特效或是獨佔模式的程式時,問題特別多。
- Encoder:編碼器,這就不用再多說了。
- Writer/Muxer:多工器,作的事剛好跟Splitter相反。
以下是數段簡單的,能夠驅動Camera的程式碼:
bool CSecRecDlg::InitGraph(void)
{
DECLARE_DBG_SCOPE(CSecRecDlg::InitGraph, true)
HRESULT hr=E_FAIL;
hr=m_gbuilder.CoCreateInstance(CLSID_FilterGraph); ASSERT(SUCCEEDED(hr));
hr=m_cgbuilder2.CoCreateInstance(CLSID_CaptureGraphBuilder2); ASSERT(SUCCEEDED(hr));
hr=m_gbuilder.QueryInterface(&m_mcontrol); ASSERT(SUCCEEDED(hr));
hr=m_cgbuilder2->SetFiltergraph(m_gbuilder); ASSERT(SUCCEEDED(hr));
AddToROT();
return true;
}
bool CSecRecDlg::PrepareGraph(wchar_t const *sFilePath)
{
DECLARE_DBG_SCOPE(CSecRecDlg::PrepareGraph, true)
CComboBox *ctrl=NULL;
CComPtr asf, rndr, sgf;
CComPtr amsconfig;
CComPtr wmprofile;
CComPtr sgrabber;
AM_MEDIA_TYPE *pSAMT=NULL, *pVAMT=NULL;
bool bUseEVR=true;
HRESULT hr=E_FAIL;
ASSERT(sFilePath);
ASSERT(m_gbuilder);
ASSERT(m_vid);
ASSERT(m_aud);
{
TCHAR buf[256]=_T("");
int n0;
double n1;
GetDlgItem(IDC_DUR)->GetWindowText(buf, 255); n0=_ttoi(buf);
(n0<1 || n0>100)?
GetDlgItem(IDC_DUR)->SetWindowText(TMSG(_T("%d\n"), m_nDurLim)):
m_nDurLim=n0;
GetDlgItem(IDC_DIFF)->GetWindowText(buf, 255); n1=_tstof(buf);
(n1<0.1 || n1>0.99)?
GetDlgItem(IDC_DIFF)->SetWindowText(TMSG(_T("%.2lf\n"), m_nDiffLim)):
m_nDiffLim=n1;
m_bIsTest=((CButton *)GetDlgItem(IDC_TEST))->GetCheck()?true:false;
m_bIsAutoOff=((CButton *)GetDlgItem(IDC_AUTOOFF))->GetCheck()?true:false;
}
//Load selected media type
if(!GetFormat(pVAMT, wmprofile))
return false;
//Video source
hr=m_gbuilder->AddFilter(m_vid, L"Video source"); ASSERT(SUCCEEDED(hr));
hr=m_cgbuilder2->FindInterface(&PIN_CATEGORY_CAPTURE, NULL, m_vid, IID_IAMStreamConfig, (void **)&amsconfig); ASSERT(SUCCEEDED(hr));
hr=amsconfig->SetFormat(pVAMT); ASSERT(SUCCEEDED(hr));
//Audio source
if(!m_bIsTest)
hr=m_gbuilder->AddFilter(m_aud, L"Audio source"); ASSERT(SUCCEEDED(hr));
//ASF writer
if(!m_bIsTest)
{
CComPtr cawriter;
hr=m_cgbuilder2->SetOutputFileName(&MEDIASUBTYPE_Asf, sFilePath, &asf, NULL); ASSERT(SUCCEEDED(hr));
hr=asf->QueryInterface(IID_IConfigAsfWriter, (void **)&cawriter); ASSERT(SUCCEEDED(hr));
hr=cawriter->ConfigureFilterUsingProfile(wmprofile); ASSERT(SUCCEEDED(hr));
hr=cawriter->SetIndexMode(TRUE); ASSERT(SUCCEEDED(hr));
}
//Video renderer
hr=rndr.CoCreateInstance(CLSID_EnhancedVideoRenderer);
{
CComPtr mfvdcontrol;
hr=MFGetService(rndr, MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl, (void **)&mfvdcontrol);
if(FAILED(hr))
rndr.Release();
}
if(FAILED(hr))
{
bUseEVR=false;
hr=rndr.CoCreateInstance(CLSID_VideoRenderer); ASSERT(SUCCEEDED(hr));
}
hr=m_gbuilder->AddFilter(rndr, L"Video renderer"); ASSERT(SUCCEEDED(hr));
//Sample grabber
hr=sgrabber.CoCreateInstance(CLSID_SampleGrabber); ASSERT(SUCCEEDED(hr));
hr=sgrabber.QueryInterface(&sgf); ASSERT(SUCCEEDED(hr));
hr=sgrabber->SetCallback(this, 0); ASSERT(SUCCEEDED(hr));
hr=m_gbuilder->AddFilter(sgf, L"Sample grabber"); ASSERT(SUCCEEDED(hr));
pSAMT=(AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); memset(pSAMT, 0, sizeof(AM_MEDIA_TYPE));
pSAMT->majortype=MEDIATYPE_Video;
pSAMT->subtype=MEDIASUBTYPE_RGB24;
hr=sgrabber->SetMediaType(pSAMT); ASSERT(SUCCEEDED(hr));
CoTaskMemFree(pSAMT);
m_nDurCnt=0; m_his.clear();
//Render streams
if(!m_bIsTest)
{
hr=m_cgbuilder2->RenderStream(&PIN_CATEGORY_CAPTURE, NULL, m_vid, NULL, asf); ASSERT(SUCCEEDED(hr));
hr=m_cgbuilder2->RenderStream(&PIN_CATEGORY_PREVIEW, NULL, m_vid, sgf, rndr);
hr=m_cgbuilder2->RenderStream(NULL, NULL, m_aud, NULL, asf); ASSERT(SUCCEEDED(hr));
}
else
hr=m_cgbuilder2->RenderStream(&PIN_CATEGORY_CAPTURE, NULL, m_vid, sgf, rndr); ASSERT(SUCCEEDED(hr));
hr=sgrabber->GetConnectedMediaType(&m_GAMT); ASSERT(SUCCEEDED(hr));
//Set video renderer
if(bUseEVR)
{
CComPtr mfvdcontrol;
CRect r;
GetDlgItem(IDC_PREVIEW)->GetWindowRect(&r); r.MoveToXY(0, 0);
hr=MFGetService(rndr, MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl, (void **)&mfvdcontrol); ASSERT(SUCCEEDED(hr));
hr=mfvdcontrol->SetVideoWindow(*GetDlgItem(IDC_PREVIEW)); ASSERT(SUCCEEDED(hr));
hr=mfvdcontrol->SetAspectRatioMode(MFVideoARMode_None); ASSERT(SUCCEEDED(hr));
hr=mfvdcontrol->SetVideoPosition(NULL, &r); ASSERT(SUCCEEDED(hr));
}
else
{
CComPtr vwindow;
CRect r;
GetDlgItem(IDC_PREVIEW)->GetWindowRect(&r);
hr=rndr.QueryInterface(&vwindow); ASSERT(SUCCEEDED(hr));
hr=vwindow->put_Left(0); ASSERT(SUCCEEDED(hr));
hr=vwindow->put_Top(0); ASSERT(SUCCEEDED(hr));
hr=vwindow->put_Width(r.Width()); ASSERT(SUCCEEDED(hr));
hr=vwindow->put_Height(r.Height()); ASSERT(SUCCEEDED(hr));
vwindow->put_WindowStyle(0);
hr=vwindow->put_Owner((OAHWND)(HWND)*GetDlgItem(IDC_PREVIEW)); ASSERT(SUCCEEDED(hr));
}
return true;
}
void CSecRecDlg::ClearGraph(void)
{
CComPtr efilters;
CComPtr bfilter;
HRESULT hr=E_FAIL;
if(!m_gbuilder)
return;
hr=m_mcontrol->Stop(); ASSERT(SUCCEEDED(hr));
DeleteMediaType(&m_GAMT);
hr=m_gbuilder->EnumFilters(&efilters); ASSERT(SUCCEEDED(hr));
while(efilters->Next(1, &bfilter, NULL)==S_OK)
{
hr=m_gbuilder->RemoveFilter(bfilter); ASSERT(SUCCEEDED(hr));
bfilter.Release();
hr=efilters->Reset(); ASSERT(SUCCEEDED(hr));
}
}
void CSecRecDlg::UninitGraph(void)
{
if(!m_gbuilder)
return;
ClearGraph();
RemoveFromROT();
m_mcontrol.Release();
m_cgbuilder2.Release();
m_gbuilder.Release();
}