視訊采集 via DirectShow
- DirectShow 簡介
- DirectShow 采集視訊
-
- 采集流程圖
- 采集代碼
-
- CVideoCap 類
-
- CVideoCap::_initCapDevice 函數
- CVideoCap::_buildCaptureGraph 函數
- CVideoCap::startPreview 函數
- CVideoCap::startRecord 函數
- CRecordSwitch 類
-
- CTransInPlaceFilter::Transform 函數
- 其他架構下的采集
DirectShow 簡介
DirectShow(有時縮寫為 DS 或 DShow),開發代号 Quartz,是微軟在 ActiveMovie 和 Video for Windows 的基礎上推出的新一代基于 COM 的流媒體處理的開發包,與 DirectX 開發包一起釋出。DShow 使用一種叫 Filter Graph 的模型來管理整個資料流的處理過程,有了 DShow,我們可以很友善地從支援 WDM 驅動模型的采集卡上捕獲資料,并且進行相應的後期處理乃至存儲到檔案中。這樣使在多媒體資料庫管理系統(MDBMS)中多媒體資料的存取變得更加友善。它廣泛地支援各種媒體格式,包括 asf、mpeg、avi、dv、mp3、wav 等,為多媒體流的捕捉和回放提供了強有力的支援。
DirectShow 采集視訊
采集流程圖
采集代碼
以下是整個 DirectShow 采集過程的概要代碼,略去各個函數的具體實作和資源釋放。
m_pBuilder = new ISampleCaptureGraphBuilder();
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (LPVOID *)&m_pFg);
m_pBuilder->SetFiltergraph(m_pFg);
CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void **) &pDevEnum);
pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnumV, 0);
pClassEnumV->Next(1, &m_pmVideo, &cFetched));
m_pmVideo->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pVCap);
m_pFg->AddFilter(m_pVCap, wachFriendlyName); // Add the video capture filter to the graph
AddFilterByCLSID(m_pFg, CLSID_SmartTee, &pTee, _T("Tee"));
m_pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pVCap, NULL, pTee);
CUnknown* pRecSwitchV = CRecordSwitch::CreateInstance(NULL, &hr);
pRecSwitchV->NonDelegatingQueryInterface(IID_IBaseFilter, (void**)&pRecSwitchVFilter);
m_pFg->AddFilter( pRecSwitchVFilter, _T("Video Rec Switch") );
m_pBuilder->SetOutputFileName(&outType, m_szCaptureFile, &pMuxer, &m_pSink);
m_pBuilder->RenderStream(NULL, NULL, pTee, pRecSwitchVFilter, pMuxer);
m_pBuilder->RenderStream(NULL, NULL, pTee, NULL, NULL); // Connect the Tee to video render
CComPtr<IMediaControl> pMC = NULL;
m_pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
pMC->Run();
m_pRecSwitchV->EnableRecord(true);
CVideoCap 類
包裝了 filter graph 的建立和調用的過程。
類定義如下:
class CVideoCap
{
public:
CVideoCap();
~CVideoCap();
HRESULT init(HWND hwndVideo);
void finalize();
HRESULT startPreview();
HRESULT stopPreview();
HRESULT startRecord();
HRESULT stopRecord(bool restartPreview = true);
void updateWindow();
void onMediaEventNotify();
private:
HRESULT _initCapDevice();
HRESULT _initCamProp();
HRESULT _buildCaptureGraph();
void _removeDownstream(IBaseFilter *pf);
void _tearDownGraph();
void _errMsg(LPTSTR szFormat,...);
private:
HWND m_hwndVideo;
CRecordSwitch* m_pRecSwitchV;
WCHAR m_szCaptureFile[_MAX_PATH];
ISampleCaptureGraphBuilder *m_pBuilder;
IVideoWindow *m_pVW;
IMediaEventEx *m_pME;
IBaseFilter *m_pVCap;
IGraphBuilder *m_pFg;
IFileSinkFilter *m_pSink;
BOOL m_fCaptureGraphBuilt;
BOOL m_fCapturing;
BOOL m_fPreviewing;
double m_frameRate;
};
CVideoCap::_initCapDevice 函數
初始化攝像頭。
HRESULT CVideoCap::_initCapDevice()
{
HRESULT hr = E_FAIL;
CComPtr <ICreateDevEnum> pDevEnum = NULL;
CComPtr <IEnumMoniker> pClassEnumV = NULL;
CComPtr<IMoniker> pMonikerV = NULL;
ULONG cFetched = 0;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void**)&pDevEnum);
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnumV, 0);
// If no enumerator for the requested type, then CreateClassEnumerator will succeed, but pClassEnum is NULL
GOTO_LABEL_IF_NULL(pClassEnumV, OnError);
while (S_OK == (pClassEnumV->Next(1, &pMonikerV, &cFetched))) {
m_pVCap = NULL;
hr = pMonikerV->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pVCap);
if (SUCCEEDED(hr))
break;
}
GOTO_LABEL_IF_NULL(m_pVCap, OnError);
return S_OK;
OnError:
_errMsg(TEXT("Cannot find a camera, please check. Error code: 0x%x"), hr);
return hr;
}
CVideoCap::_buildCaptureGraph 函數
建構采集用的 filter graph。
HRESULT CVideoCap::_buildCaptureGraph()
{
HRESULT hr = E_FAIL;
CComPtr<IBaseFilter> pTee;
CComPtr<IBaseFilter> pRecSwitchVFilter;
if (m_fCaptureGraphBuilt)
return S_OK;
if (m_fCapturing || m_fPreviewing)
return S_FALSE;
m_pBuilder = new ISampleCaptureGraphBuilder();
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (LPVOID *)&m_pFg);
hr = m_pBuilder->SetFiltergraph(m_pFg);
hr = m_pFg->AddFilter(m_pVCap, _T("Video Capture"));
hr = AddFilterByCLSID(m_pFg, CLSID_SmartTee, &pTee, _T("Tee"));
hr = m_pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pVCap, NULL, pTee);
if (FAILED(hr))
hr = m_pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, m_pVCap, NULL, pTee);
CUnknown* pRecSwitchV = CRecordSwitch::CreateInstance(NULL, &hr);
hr = pRecSwitchV->NonDelegatingQueryInterface(IID_IBaseFilter, (void**)&pRecSwitchVFilter);
hr = m_pFg->AddFilter( pRecSwitchVFilter, _T("Video Rec Switch") );
CComPtr<IBaseFilter> pMuxer;
CComPtr<IConfigAviMux> pConfigAviMux;
createCaptureFile(m_szCaptureFile, _countof(m_szCaptureFile));
hr = m_pBuilder->SetOutputFileName(&outType, m_szCaptureFile, &pMuxer, &m_pSink);
hr = pMuxer->QueryInterface(IID_IConfigAviMux, (void **)&pConfigAviMux);
if(hr == NOERROR && pConfigAviMux)
pConfigAviMux->SetOutputCompatibilityIndex(TRUE);
hr = m_pBuilder->RenderStream(NULL, NULL, pTee, pRecSwitchVFilter, pMuxer);
hr = m_pBuilder->RenderStream(NULL, NULL, pTee, NULL, NULL); // Connect the Tee to video render
hr = m_pFg->QueryInterface(IID_IVideoWindow, (void **)&m_pVW);
m_pVW->put_Owner((OAHWND)m_hwndVideo); // We own the window now
m_pVW->put_WindowStyle(WS_CHILD); // you are now a child
RECT rc;
GetClientRect(m_hwndVideo, &rc);
int cy = GetSystemMetrics(SM_CYBORDER);
rc.bottom -= cy;
m_pVW->SetWindowPosition(0, 0, rc.right, rc.bottom);
m_pVW->put_Visible(OATRUE);
CComPtr<IAMStreamConfig> pVSC;
AM_MEDIA_TYPE *pmt = NULL;
hr = m_pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pVCap, IID_IAMStreamConfig, (void**)&pVSC);
if (hr != NOERROR)
m_pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, m_pVCap, IID_IAMStreamConfig, (void**)&pVSC);
if (NULL != pVSC) {
hr = pVSC->GetFormat(&pmt);
if (hr == NOERROR) {
if (pmt->formattype == FORMAT_VideoInfo) {
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
pvi->AvgTimePerFrame = (LONGLONG)(10000000 / m_frameRate);
hr = pVSC->SetFormat(pmt);
}
DeleteMediaType(pmt);
}
}
hr = m_pFg->QueryInterface(IID_IMediaEventEx, (void **)&m_pME);
hr = m_pME->SetNotifyWindow((OAHWND)m_hwndVideo, WM_FGNOTIFY, 0);
return S_OK;
}
CVideoCap::startPreview 函數
開啟攝像頭預覽。
HRESULT CVideoCap::startPreview()
{
if (m_fPreviewing)
return S_FALSE;
CComPtr<IMediaControl> pMC = NULL;
HRESULT hr = m_pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
RETURN_IF_FAILED(hr);
hr = pMC->Run();
GOTO_LABEL_IF_FAILED(hr, OnErr);
m_fPreviewing = TRUE;
return S_OK;
OnErr:
pMC->Stop();
return hr;
}
CVideoCap::startRecord 函數
開始攝像頭錄制。在DShow 提供的 AmCap sample 中,開始錄制需要重新建構 filter graph,此處我們使用了 Tee + 開關的形式,是以可以直接錄制。
HRESULT CVideoCap::startRecord()
{
if (m_pRecSwitchV != NULL)
m_pRecSwitchV->EnableRecord(true);
m_fCapturing = TRUE;
return S_OK;
}
CRecordSwitch 類
開關錄制的 filter,繼承自 CTransInPlaceFilter。
類定義如下:
// A filter to switch on/off video recording
class CRecordSwitch : public CTransInPlaceFilter // Main DirectShow interfaces
{
public:
static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);
DECLARE_IUNKNOWN;
HRESULT CheckInputType(const CMediaType *mtIn);
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
void EnableRecord(bool enable);
private:
CRecordSwitch(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);
// Overrides the PURE virtual Transform of CTransInPlaceFilter base class
// This is where the "real work" is done.
HRESULT Transform(IMediaSample *pSample);
// Overrides a CTransformInPlace function. Called as part of connecting.
virtual HRESULT SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt);
private:
bool m_bEnableRec;
};
CTransInPlaceFilter::Transform 函數
Transform 已經不能再簡單了。
HRESULT CRecordSwitch::Transform(IMediaSample *pSample)
{
CheckPointer(pSample, E_POINTER);
if (m_bEnableRec)
return NOERROR;
else
return S_FALSE;
}
其他架構下的采集
請參考對應的文章。
- FFmpeg
- Media Foundation
– EOF –