天天看點

視訊采集 via DirectShowDirectShow 簡介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 DirectShowDirectShow 簡介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
視訊采集 via DirectShowDirectShow 簡介DirectShow 采集視訊其他架構下的采集

– EOF –

繼續閱讀