DirectShow(有时缩写为 DS 或 DShow),开发代号 Quartz,是微软在 ActiveMovie 和 Video for Windows 的基础上推出的新一代基于 COM 的流媒体处理的开发包,与 DirectX 开发包一起发布。DShow 使用一种叫 Filter Graph 的模型来管理整个数据流的处理过程,有了 DShow,我们可以很方便地从支持 WDM 驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。这样使在多媒体数据库管理系统(MDBMS)中多媒体数据的存取变得更加方便。它广泛地支持各种媒体格式,包括 asf、mpeg、avi、dv、mp3、wav 等,为多媒体流的捕捉和回放提供了强有力的支持。
以下是整个 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);
包装了 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;
};
初始化摄像头。
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;
}
构建采集用的 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;
}
开启摄像头预览。
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;
}
开始摄像头录制。在DShow 提供的 AmCap sample 中,开始录制需要重新构建 filter graph,此处我们使用了 Tee + 开关的形式,因此可以直接录制。
HRESULT CVideoCap::startRecord()
{
if (m_pRecSwitchV != NULL)
m_pRecSwitchV->EnableRecord(true);
m_fCapturing = TRUE;
return S_OK;
}
开关录制的 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;
};
Transform 已经不能再简单了。
HRESULT CRecordSwitch::Transform(IMediaSample *pSample)
{
CheckPointer(pSample, E_POINTER);
if (m_bEnableRec)
return NOERROR;
else
return S_FALSE;
}
请参考对应的文章。
– EOF –