视频采集 via DirectShow

本文介绍DirectShow,微软的流媒体处理开发包,用于视频采集、处理及存储。通过CVideoCap类封装,实现摄像头初始化、预览、录制等功能。CRecordSwitch类用于控制视频录制的开关。

DirectShow 简介

DirectShow(有时缩写为 DS 或 DShow),开发代号 Quartz,是微软在 ActiveMovie 和 Video for Windows 的基础上推出的新一代基于 COM 的流媒体处理的开发包,与 DirectX 开发包一起发布。DShow 使用一种叫 Filter Graph 的模型来管理整个数据流的处理过程,有了 DShow,我们可以很方便地从支持 WDM 驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。这样使在多媒体数据库管理系统(MDBMS)中多媒体数据的存取变得更加方便。它广泛地支持各种媒体格式,包括 asf、mpeg、avi、dv、mp3、wav 等,为多媒体流的捕捉和回放提供了强有力的支持。

DirectShow 采集视频

采集流程图

dshow cap v

采集代码

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

其他框架下的采集

请参考对应的文章。

Blueware
EOF

/---------------------------------------------------------------------\ * 书 名:《DirectShow开发指南》 * 作 者: 陆其明(著) 金邦飞(审校) * 内容提要: 本书以DirectX SDK 9.0版为蓝本,涉及的内容几乎涵盖了在Windows平台上使 用DirectShow进行C++编码的方方面面。全书共分4个部分。第1部分详细介绍了 DirectShow的基础知识。第2部分重点讨论了Filter的开发,以及DirectShow 应用程序的开发,包括目前非常流行的如音视频采集、数码摄像机的支持、非线性 编辑等应用。第3部深入分析了DirectShow SDK提供的部分典型源代码例子。第 4部分结合作者个人的一些开发实践,通过案例和开放源码分析,进一步介绍 DirectShow的实务应用。 本书完全忠实于DirectX SDK 9.0的帮助文档以及基类源代码,并结合作者多年 的实践,经过提炼而成。内容丰富,条理清晰,实用性强。适合广大的流媒体应用 开发人员,以及对Windows平台上多媒体处理感兴趣的编程爱好者、学生学习和参 考。 * 下载内容说明: readme.txt:本说明文件。 Chapter04:第4章用到的代码,其中AppIPTransform为MFC Filter的例子, DsDemo是一些演示代码,FilterTitleOverlay是字符叠加Filter的源代码。 Chapter05:第5章用到的代码,其中GraphBuilding为Filter Graph构建技术 的一些代码,SimplePlayer为一个简单的播放器例子。 Chapter07:第7章用到的代码,其中DsDemo是一些演示代码。 Chapter09:第9章用到的代码,其中DESCallback演示了控制DirectShow智能 连接的方法。 Chapter18:第18章用到的代码,其中MpegNetwork为“MPEG流的网络客户端播放” 实现的所有源代码(请打开Daisy.dsw浏览各项目)。 Chapter19:第19章用到的代码,其中DVD2AVI_1.77.3_SRC.zip为开放源码, FilterMpeg2VD为此开放源码基础上开发的MPEG-2 Video Decoder Filter例子。 除上述代码外,本书各章用到的其他代码均在DirectX SDK安装目录的Samples 子目录下可以找到。 * 备注: DirectX SDK 9.0以及DirectX 9.0运行时库可以到微软的网站上下载。 请访问微软的网站首页http://www.microsoft.com,然后输入DirectX进行搜 索;或者直接访问http://www.microsoft.com/directx下载运行时库,或者 http://www.microsoft.com/downloads/details.aspx?FamilyId=9216652F-51E0-402E-B7B5-FEB68D00F298&displaylang=en * 技术支持网站:http://hqtech.nease.net \---------------------------------------------------------------------/
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值