视频采集 via DirectShow

视频采集 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 采集视频

采集流程图

视频采集 via DirectShow_第1张图片

采集代码

以下是整个 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

Blueware
EOF

你可能感兴趣的:(Multimedia,多媒体开发,视频采集,视频录制,摄像头,DirectShow,DShow)