分享

DirectShow的中文资料之建立一个捕捉程序 zz设备列举和捕捉接口

 haodafeng_org 2011-04-10

在看DXSDK\Samples\C++\DirectShow\Capture\AMCap工程时,发现了这个,于是把它转载过来了

   
DirectShow 提供了用应用程序从适当的硬件中捕捉和预览音/视频的能力。数据源包括:VCRcamera,TV tuner,microphone,或其他的数据源。一个应用程序可以立刻显示捕捉的数据(预览),或是保存到一个文件中。

在这个例子中,ICaptureGraphBuilder 接口是处理捕捉工作的主要接口。你可以在你自己的捕捉程序中使用同样的方法和接口。在这里主要讨论ICaptureGraphBuilder 如何执行音/视频的捕捉。我们假设你已经熟悉了DirectShowfilter graph的体系和一般的capture filter graph的结构(可以参考DirectShow基础指南)

ICaptureGraphBuilder 接口提供了一个filter graph builder对象,让你的应用程序在建立capture filter graph时,省去处理很多单调乏味的工作,集中精力于捕捉中。他提供的方法满足了基本的捕捉和预览功能的要求。

方法FindInterface -- filter graph中查找一个于捕捉有关的详细的接口。使的你可以访
                      
问一个详细接口的功能,而不需要你去列举在filter graph中的pins
                      
filters
方法RenderStream -- 连接源过滤器和渲染过滤器,选择添加一些中间的过滤器。
方法ControlStream -- 独立的精确的控制graph的开始和结束帧。
  

既然是硬件捕捉,当然要和硬件打交道,接着介绍设备列举和捕捉接口
通过ICreateDevEnum::CreateClassEnumerator方法列举捕捉系统中的设备。之后,实例化一个DirectShowfilter去使用这个设备。接着用ICaptureGraphBuilder::FindInterface去获得于捕捉相关的接口指针IAMDroppedFrames, IAMVideoCompression, IAMStreamConfig, and IAMVfwCaptureDialogs 。因为设备列举和捕捉接口比较长,放在这会打乱结构,所有专门写了一篇(参考设备列举和捕捉接口)

NOTE
1.这个示例是DirectShow自带的例子。你可以在DirectShow SDK的目录Sample\DS\Caputre看这个例子代码(AMCap.cpp)。这里只是他的一些片断代码。可以说是他的中文模块的说明。
2.AMCap例子中,把所有的接口指针和一些成员变量保存在一个全局结构gcap中了。
定义如下:

struct _capstuff {
    char                  szCaptureFile[_MAX_PATH];
    WORD                  wCapFileSize;  // size in Meg
    ICaptureGraphBuilder  *pBuilder;
    IVideoWindow          *pVW;
    IMediaEventEx         *pME;
    IAMDroppedFrames      *pDF;
    IAMVideoCompression   *pVC;
    IAMVfwCaptureDialogs  *pDlg;
    IAMStreamConfig       *pASC;      // for audio cap
    IAMStreamConfig       *pVSC;      // for video cap
    IBaseFilter           *pRender;
    IBaseFilter           *pVCap, *pACap;
    IGraphBuilder         *pFg;
    IFileSinkFilter       *pSink;
    IConfigAviMux         *pConfigAviMux;
    int                   iMasterStream;
    BOOL                  fCaptureGraphBuilt;
    BOOL                  fPreviewGraphBuilt;
    BOOL                  fCapturing;
    BOOL                  fPreviewing;
    BOOL                  fCapAudio;
    int                   iVideoDevice;
    int                   iAudioDevice;
    double                FrameRate;
    BOOL                  fWantPreview;
    long                  lCapStartTime;
    long                  lCapStopTime;
    char                  achFriendlyName[120];
    BOOL                  fUseTimeLimit;
    DWORD                 dwTimeLimit;
} gcap;

当不在需要保存在gcap中的接口指针是,一定要释放这些接口指针,一般是在程序的析构函数中,或是在别的同等功能函数中。如下:

if (gcap.pBuilder)
    gcap.pBuilder->Release();
    gcap.pBuilder = NULL;
if (gcap.pSink)
    gcap.pSink->Release();
    gcap.pSink = NULL;
if (gcap.pConfigAviMux)
    gcap.pConfigAviMux->Release();
    gcap.pConfigAviMux = NULL;
if (gcap.pRender)
    gcap.pRender->Release();
    gcap.pRender = NULL;
if (gcap.pVW)
    gcap.pVW->Release();
    gcap.pVW = NULL;
if (gcap.pME)
    gcap.pME->Release();
    gcap.pME = NULL;
if (gcap.pFg)
    gcap.pFg->Release();
    gcap.pFg = NULL;

设置文件名
使用普通的OpenFile dialog获得捕捉文件的信息。通过调用AllocCaptureFile 函数为捕捉文件分配空间。这一点是重要的,因为这是个巨大的空间。这样可以提高捕捉操作的速度。ICaptureGraphBuilder::AllocCapFile 执行实际的文件分配,IFileSinkFilter::SetFileName 指示file writer filter使用用户选择的文件名保存数据。ICaptureGraphBuilder::SetOutputFileName file writer filter加入filter graph(后面会介绍,他是ICaptureGraphBuilderd自带的)

SetCaptureFile AllocCaptureFile 函数如下:

BOOL SetCaptureFile(HWND hWnd)
{
    if (OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH))
    {
        OFSTRUCT os;
        // We have a capture file name
       
        if (OpenFile(gcap.szCaptureFile, &os, OF_EXIST) == HFILE_ERROR)
        {
             // Bring up dialog, and set new file size
             BOOL f = AllocCaptureFile(hWnd);
             if (!f)
                 return FALSE;
        }
    }
    else
    {
         return FALSE;
    }

    SetAppCaption(); // need a new app caption

    // Tell the file writer to use the new file name
    if (gcap.pSink)
    {
        WCHAR wach[_MAX_PATH];
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH);
        gcap.pSink->SetFileName(wach, NULL);
    }


    return TRUE;
}

// Preallocate the capture file
//
BOOL AllocCaptureFile(HWND hWnd)
{
    // We'll get into an infinite loop in the dlg proc setting a value
    if (gcap.szCaptureFile[0] == 0)
        return FALSE;

   
    if (DoDialog(hWnd, IDD_AllocCapFileSpace, AllocCapFileProc, 0))
    {
        // Ensure repaint after dismissing dialog before
        // possibly lengthy operation
        UpdateWindow(ghwndApp);

        // User has hit OK. Alloc requested capture file space
        BOOL f = MakeBuilder();
        if (!f)
            return FALSE;
        WCHAR wach[_MAX_PATH];
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH);
        if (gcap.pBuilder->AllocCapFile(wach, gcap.wCapFileSize * 1024L * 1024L) != NOERROR)
        {
            MessageBoxA(ghwndApp, "Error", "Failed to pre-allocate capture file space", MB_OK | MB_ICONEXCLAMATION);
            return FALSE;
        }

        return TRUE;
    }
    else
    {
        return FALSE;
    }

}

建立Graph Builder对象

AMCap MakeBuilder函数建立了一个capture graph builer对象,通过调用CoCreateInstance获得了ICaptureGraphBuilder 接口指针。AMCap把他存储到gcap结构的pBuilder中。
// Make a graph builder object we can use for capture graph building
BOOL MakeBuilder()
{
    // We have one already
    if (gcap.pBuilder)
        return TRUE;

    HRESULT hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder, NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder, (void **)&gcap.pBuilder);
    return (hr == NOERROR) ? TRUE : FALSE;
}


建立Graph的渲染部分,并告诉他写文件(用先前决定的文件)

这包括一个multiplexer filter file writerDirectShow 提供了一个AVI MUX(multiplexer)filter
在这里ICaptureGraphBuilder::SetOutputFileName 是一个关键的方法。他把multiplexer file writer添加到filter graph中,连接他们,并设置文件的名字。第一个参数MEDIASUBTYPE_Avi,指出capture graph builder 对象将插入一个AVI multiplexer filter,因此,file writer将以AVI文件格式记录捕捉的数据。第二个参数(wach)是文件名最后的两个参数指出multiplexer filter (gcap.pRender) file writer filter (gcap.pSink),这两个是通过SetOutputFileName 函数初始化的。AMCap存储这些指针到全局结构gcap中。capture graph builder 对象建立了一个filter graph对象(IGraphBuilder),把这两个filter加入到filter graph中去。他告诉file writer使用指定的文件保存数据。下面的例子演示了如何调用SetOutputFileName
// We need a rendering section that will write the capture file out in AVI
// file format
    WCHAR wach[_MAX_PATH];
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH);
    GUID guid = MEDIASUBTYPE_Avi;
    hr = gcap.pBuilder->SetOutputFileName(&guid, wach, &gcap.pRender, &gcap.pSink);
    if (hr != NOERROR)
    {
        ErrMsg("Error %x: Cannot set output file", hr);
        goto SetupCaptureFail;
    }


获得当前的Filter Graph

因为在调用SetOutputFileName中,capture graph builder 对象建立了一个filter graph,所有你必须把需要的filter加入同一个filter graph 中。通过ICaptureGraphBuilder::GetFiltergraph获得新建立的filter graph。返回的指针是参数gcap.pFg
// The graph builder created a filter graph to do that.  Find out what it is,
// and put the video capture filter in the graph too.

    hr = gcap.pBuilder->GetFiltergraph(&gcap.pFg);
    if (hr != NOERROR)
    {
        ErrMsg("Error %x: Cannot get filtergraph", hr);
        goto SetupCaptureFail;
    }

 

添加音/视频过滤器到当前的Filter Graph
    hr = gcap.pFg->AddFilter(gcap.pVCap, NULL);
    if (hr != NOERROR)
    {
        ErrMsg("Error %x: Cannot add vidcap to filtergraph", hr);
        goto SetupPreviewFail;
    }

    hr = gcap.pFg->AddFilter(gcap.pACap, NULL);
    if (hr != NOERROR)
    {
        ErrMsg("Error %x: Cannot add audcap to filtergraph", hr);
        goto SetupCaptureFail;
    }


渲染视频捕捉过滤器的Capture Pin和音频捕捉的Capture Pin
ICaptureGraphBuilder::RenderStream 连接源过滤器的pin到渲染过滤器。pin的类别是可选的,capture pin (PIN_CATEGORY_CAPTURE) preview pin (PIN_CATEGORY_PREVIEW)。下面的例子演示了连接video capture filter (gcap.pVCap) capture pin到渲染gcap.pRender中。
// Render the video capture and preview pins - we may not have preview, so
// don't worry if it doesn't work
hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, NULL, gcap.pVCap, NULL, gcap.pRender);

    // Error checking

再次ICaptureGraphBuilder::RenderStream 连接audio capture filter (gcap.pACap) 到渲染audio renderer 中。
// Render the audio capture pin?   
if (gcap.fCapAudio)

    {
        hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, NULL, gcap.pACap, NULL, gcap.pRender);
    // Error checking

渲染Video Capture Filter Preview Pin
再次调用ICaptureGraphBuilder::RenderStream,从capture filterpreview pinvideo renderer。代码如下:

    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, NULL, gcap.pVCap, NULL, NULL);

获得访问Video Preview Window的接口指针
缺省的,video preview window是一个独立的窗口。如果你想改变默认的行为,先调用 ICaptureGraphBuilder::FindInterface获得IVideoWindow 接口。第二个参数通过gcap.pVCap指定,描述video capture filter,第三个参数是想得到的接口(IVideoWindow),最后的是返回的接口。当你得到IVideoWindow接口后,你可以调用IVideoWindow的方法象put_Owner, put_WindowStyle, or SetWindowPosition 去获得video preview windowhandle,设置窗口属性,或把他放到想要的位置。

// This will go through a possible decoder, find the video renderer it's
// connected to, and get the IVideoWindow interface on it
   hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, gcap.pVCap, IID_IVideoWindow, (void **)&gcap.pVW);
    if (hr != NOERROR)
    {
        ErrMsg("This graph cannot preview");
    }

    else
    {
        RECT rc;
        gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now
        gcap.pVW->put_WindowStyle(WS_CHILD);    // you are now a child
        // give the preview window all our space but where the status bar is
        GetClientRect(ghwndApp, &rc);
        cyBorder = GetSystemMetrics(SM_CYBORDER);
        cy = statusGetHeight() + cyBorder;
        rc.bottom -= cy;
        gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
        gcap.pVW->put_Visible(OATRUE);
    }

现在你已经建立完整的capture filter graph了,你可以预览音频,视频,或捕捉数据。


控制 Capture Filter Graph

因为通过ICaptureGraphBuilder接口构造的capture filter graph 只是一个简单的专门用途的filter graph,所有,控制他就象控制其他类型的filter graph一样。你可以使用IMediaControl interface Run, Pause, and Stop方法,你也可以使用CBaseFilter::Pause的方法。另外ICaptureGraphBuilder提供了ControlStream方法去控制capture filter graphstreams的开始和结束时间。ControlStream调用IAMStreamControl::StartAt IAMStreamControl::StopAt控制filter graph的捕捉和预览的开始和结束的位置。
注意:不是所有的capture filter都可以,因为不是每一个capture filter都支持IAMStreamControl

ICaptureGraphBuilder::ControlStream方法的第一个参数(pCategory)是一个输出pin类的GUID。这个变量通常是PIN_CATEGORY_CAPTURE PIN_CATEGORY_PREVIEW。指定为NULL则控制所有的capture filter第二个参数(pFilter)指出那个filter控制。NULL说明为控制所有的filter graph。如果只是预览(防止捕捉)的话,可以调用ICaptureGraphBuilder::ControlStream,参数用capture pin类型,MAX_TIME作为开始时间(第三个参数pstart)。再次调用该方法,参数用preview pin类型,NULL作为开始时间则立即开始预览。第四参数指出结束的时间(pstop),含义和第三个参数一样(NULL意味着立刻)MAX_TIMEDirectShow中定义为最大的参考时间。在这里意味着忽略或取消指定的操作。最后的参数wStartCookiewStopCookie分别是开始和结束的cookies(不知道该怎么翻译,因为我也不理解这个参数的含义)
下面的代码设置立刻开始预览,但是忽略捕捉。

    // Let the preview section run, but not the capture section
    // (There might not be a capture section)
    REFERENCE_TIME start = MAX_TIME, stop = MAX_TIME;

    // show us a preview first? but don't capture quite yet...
    hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW, NULL, gcap.fWantPreview ? NULL : &start, gcap.fWantPreview ? &stop : NULL, 0, 0);
    if (SUCCEEDED(hr))
        hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, &start, NULL, 0, 0);

同样的,如果你只想要捕捉而不要预览,设置捕捉的开始时间为NULL,设置捕捉的结束时间为MAX_TIME。设置预览的开始时间为MAX_TIMENULL为结束时间。

下面的例子告诉filter graph开始预览(第三个参数:开始时间为NULL)。结束时间指定为MAX_TIME意味着忽视停止时间(永远放下去)

  gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW, NULL, NULL, MAX_TIME, 0, 0);

调用IMediaControl::Run 运行 graph

    // Run the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr))
    {
        hr = pMC->Run();
        if (FAILED(hr))
        {
            // Stop parts that ran
            pMC->Stop();
        }

        pMC->Release();
    }
    if (FAILED(hr))
    {
        ErrMsg("Error %x: Cannot run preview graph", hr);
        return FALSE;
    }


如果graph已经运行,通过调用ICaptureGraphBuilder::ControlStream立刻开始捕捉。例如下面的代码,控制整个的filter graph(第二个参数为NULL),立刻开始(第三个参数是NULL),并且永不停止(第四个参数是MAX_TIME)

    // NOW!
 gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, MAX_TIME, &stop, 0, 0);

停止预览或捕捉操作,调用IMediaControl::Stop,就同你调用IMediaControl::Run一样。

    // Stop the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr))
    {
        hr = pMC->Stop();
        pMC->Release();
    }


获得捕捉的信息

通过IAMDroppedFrames接口获得。测试丢失帧的数量(IAMDroppedFrames::GetNumDropped),捕捉的数量(IAMDroppedFrames::GetNumNotDropped)IAMDroppedFrames::GetAverageFrameSize方法提供了捕捉帧的平均尺寸(单位:byte)。使用这些信息可以知道总的捕捉字节和每秒的帧数(速率)


保存文件

最初分配的捕捉文件只是临时的保存数据,所有你可以尽可能快的捕捉。当你想把捕捉的数据保存到硬盘中时,调用ICaptureGraphBuilder::CopyCaptureFile。这个方法从先前得到的捕捉文件输出数据到你选择的另一个文件中。这个新的储存文件的大小是和实际捕捉的数据匹配的,而不是和先前的文件大小匹配。
    
ICaptureGraphBuilder::CopyCaptureFile方法的第一个参数是复制源,第二个是目标文件。第三个参数设为TRUE指出用户允许用ESC键中断复制操作。最后参数是可选的。允许你提供一个进程指示器。如果想要的化,通过执行 IAMCopyCaptureFileProgress 接口。下面示例了如何调用CopyCaptureFile

    hr = pBuilder->CopyCaptureFile(wachSrcFile, wachDstFile,TRUE,NULL);

通过普通的Open File dialog得到新的文件名。用MultiByteToWideChar 函数把文件名转成wide string,使用ICaptureGraphBuilder::CopyCaptureFile把捕捉的数据保存到指定的文件中。


BOOL SaveCaptureFile(HWND hWnd)
{
    HRESULT hr;
    char achDstFile[_MAX_PATH];
    WCHAR wachDstFile[_MAX_PATH];
    WCHAR wachSrcFile[_MAX_PATH];

    if (gcap.pBuilder == NULL)
        return FALSE;

    if (OpenFileDialog(hWnd, achDstFile, _MAX_PATH))
    {
        // We have a capture file name
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wachSrcFile, _MAX_PATH);
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, achDstFile, -1, wachDstFile, _MAX_PATH);
        statusUpdateStatus(ghwndStatus, "Saving capture file - please wait...");

        // We need our own graph builder because the main one might not exist
        ICaptureGraphBuilder *pBuilder;
        hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder, NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder, (void **)&pBuilder);
        if (hr == NOERROR)
        {
            // Allow the user to press ESC to abort... don't ask for progress
            hr = pBuilder->CopyCaptureFile(wachSrcFile, wachDstFile,TRUE,NULL);
            pBuilder->Release();
        }

        if (hr == S_OK)
            statusUpdateStatus(ghwndStatus, "Capture file saved");
        else if (hr == S_FALSE)
            statusUpdateStatus(ghwndStatus, "Capture file save aborted");
        else
            statusUpdateStatus(ghwndStatus, "Capture file save ERROR");
        return (hr == NOERROR ? TRUE : FALSE);
    }
    else
    {
        return TRUE;    // They canceled or something
    }

}

关于捕捉媒体文件和获得捕捉信息的详细内容,可以参考AMCap例子的Amcap.cpp Status.cpp

 


DirectShow的中文资料之设备列举和捕捉接口 zz


这篇解释和示例如何通过DirectShow的接口去初始化和访问系统的硬件设备。代表性的,DirectShow应用程序使用下面类型的硬件。

/视频捕捉卡
音频或视频回放卡
音频或视频压缩或解压卡(MPEG解码器)
下面将以AV设备作参考。


如何列举设备

 

  DirectShow SDK中的接口,类,和例子提供了音/视频捕捉和回放的功能。因为文件源过滤器和filter graph manager处理了内在的工作,所有,添加捕捉功能到一个应用程序中,只需添加很少的代码。你可以通过列举系统硬件设备和得到设备列表完成特别的任务(例如:所有的视频捕捉卡的列表)DirectShow自动为win32Video for Windows 设备实例化过滤器。

AV设备工作,首先,你必须检测当前系统存在的设备。ICreateDevEnum接口建立指定类型的列表。提供你需要的检测和设置硬件的功能。访问一个指定的设备有三步,详细的说明和代码如下:

建立系统硬件设备的列表


   首先,申明一个列表指针,然后通过 CoCreateInstance 建立。CLSID_SystemDeviceEnum是我们想建立对象的类型,IID_ICreateDevEnum是接口的GUID

    ICreateDevEnum  *pCreateDevEnum ;
    CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum);
   其次,建立一个特别类型的硬件设备的列表(例如视频捕捉卡)
申明一个IEnumMoniker接口,并把他传给ICreateDevEnum::CreateClassEnumerator 方法。你就可以使用他访问新得到的列表了。

    IEnumMoniker *pEnumMon ;
    pCreateDevEnum->CreateClassEnumerator( [specify device GUID here], &pEnumMon, 0);

  最后,列举列表直到你得到你想要的设备为止。
如果先前的CreateClassEnumerator调用成功了,你可以用IEnumMoniker::Next得到设备。调用IMoniker::BindToObject建立一个和选择的device联合的filter,并且装载filter的属性(CLSID,FriendlyName, and DevicePath)。不需要为if语句的(1 == cFetched) 困惑,在测试合法性之前,pEnumMon->Next(1, &pMon, &cFetched)方法会设置他为返回对象的数字(如果成功了为1)

    ULONG cFetched = 0;
    IMoniker *pMon ;

    if (S_OK == (pEnumMon->Next(1, &pMon, &cFetched))  &&  (1 == cFetched))
    {
        pMon->BindToObject(0, 0, IID_IBaseFilter, (void **)&[desired interface here]) ;

 

   好,现在你有了一个IMoniker指针,你可以添加设备的filterfilter graph。一旦你添加了filter,你就不需要IMoniker指针,设备列表,或系统设备列表。

        pGraph->AddFilter([desired interface here], L"[filter name here]") ;
        pMon->Release() ;  // Release moniker
    }
    pEnumMon->Release() ; // Release the class enumerator
    }
    pCreateDevEnum->Release();

 

实例:AMCap中的设备列表代码

AMCap例子中,把所有的接口指针和一些成员变量保存在一个全局结构gcap中了。
定义如下:
struct _capstuff {
    char                 szCaptureFile[_MAX_PATH];
    WORD                 wCapFileSize;  // size in Meg
    ICaptureGraphBuilder *pBuilder;
    IVideoWindow         *pVW;
    IMediaEventEx        *pME;
    IAMDroppedFrames     *pDF;
    IAMVideoCompression  *pVC;
    IAMVfwCaptureDialogs *pDlg;
    IAMStreamConfig      *pASC;      // for audio cap
    IAMStreamConfig      *pVSC;      // for video cap
    IBaseFilter          *pRender;
    IBaseFilter          *pVCap, *pACap;
    IGraphBuilder        *pFg;
    IFileSinkFilter      *pSink;
    IConfigAviMux        *pConfigAviMux;
    int                  iMasterStream;
    BOOL                 fCaptureGraphBuilt;
    BOOL                 fPreviewGraphBuilt;
    BOOL                 fCapturing;
    BOOL                 fPreviewing;
    BOOL                 fCapAudio;
    int                  iVideoDevice;
    int                  iAudioDevice;
  double               FrameRate;
    BOOL                 fWantPreview;
    long                 lCapStartTime;
    long                 lCapStopTime;
    char                 achFriendlyName[120];
    BOOL                 fUseTimeLimit;
    DWORD                dwTimeLimit;
} gcap;


例子用uIndex变量循环列举系统的硬件设备。
BOOL InitCapFilters()
{
    HRESULT hr;
    BOOL f;
    UINT uIndex = 0;

MakeBuilder函数建立了一个filter graph builder(参考建立一个捕捉程序)

    f = MakeBuilder();

建立设备列表对象,得到ICreateDevEnum接口

    ICreateDevEnum *pCreateDevEnum;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum);

建立一个特别类型的硬件设备的列表,类的IDCLSID_VideoInputDeviceCategory。现在有了一个IEnumMoniker指针,可以访问捕捉设备的列表了。

    IEnumMoniker *pEm;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
    pCreateDevEnum->Release();   // We don't need the device enumerator anymore
    pEm->Reset();      // Go to the start of the enumerated list 

   现在需要实际的设备了,调用IEnumMoniker::Next ,然后用得到的指针pM调用IMoniker::BindToObject,绑定filter到设备。如果你不想建立联合的filter,使用IMoniker::BindToStorage 代替IMoniker::BindToObject

ULONG cFetched;
    IMoniker *pM;     // This will access the actual devices
    gcap.pVCap = NULL;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
        if ((int)uIndex == gcap.iVideoDevice)
        {
             // This is the one we want.  Instantiate it.
             hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap);
             pM->Release();   // We don't need the moniker pointer anymore
             break;

  }
        pM->Release();
        uIndex++;
    }
    pEm->Release();   // We've got the device; don't need the enumerator anymore

    当有了设备后,通过接口指针去测量帧数,得到driver的名字,得到捕捉的尺寸(size)。在例子中,把每个指针都存储才gcap全局结构中了。
, and get the capture size. AMCap stores each pointer in the gcap global structure.

    // We use this interface to get the number of captured and dropped frames
    gcap.pBuilder->FindCaptureInterface(gcap.pVCap, IID_IAMDroppedFrames, (void **)&gcap.pDF);

    // We use this interface to get the name of the driver
    gcap.pBuilder->FindCaptureInterface(gcap.pVCap, IID_IAMVideoCompression, (void **)&gcap.pVC);

    // We use this interface to set the frame rate and get the capture size
    gcap.pBuilder->FindCaptureInterface(gcap.pVCap, IID_IAMVideoStreamConfig, (void **)&gcap.pVSC);

 

然后得到媒体的类型和显示窗口的大小去匹配视频格式的尺寸。

    AM_MEDIA_TYPE *pmt;
    gcap.pVSC->GetFormat(&pmt);   // Current capture format

    ResizeWindow(HEADER(pmt->pbFormat)->biWidth, HEADER(pmt->pbFormat)->biHeight);
    DeleteMediaType(pmt);


   现在,已经有了视频设备和他的相关信息,重复这个过程,得到音频设和他的信息并存储到全局机构中去。注意,这次是用参数CLSID_AudioInputDeviceCategory 调用ICreateDevEnum::CreateClassEnumerator

    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,

  (void**)&pCreateDevEnum);
    uIndex = 0;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory&pEm, 0);
    pCreateDevEnum->Release();

    pEm->Reset();
    gcap.pACap = NULL;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
        if ((int)uIndex == gcap.iAudioDevice)
        {
            // this is the one we want
            hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pACap);
            pM->Release();
            break;
        }
        pM->Release();
        uIndex++;
    }
    pEm->Release();

AMCap also repeats the process of retrieving the format interface, this time for the audio device.

    hr = gcap.pBuilder->FindCaptureInterface(gcap.pACap, IID_IAMAudioStreamConfig, (void **)&gcap.pASC);
}

 

何保持DirectShow Filter Properties 道具

 IPropertyBag IPersistPropertyBag 接口存储和返回Properties"bags"组。通过这些接口存储的Properties是可以持久保持的。同一个对象在不同的实例之间,他们保持一致。Filter可以存储他们的Properties(CLSID, FriendlyName, and DevicePath)。当一个filter存储完他的Properties之后,实例一个filter时,DirectShow会自动得到他们。添加功能到你的filter中,执行IPersistPropertyBag接口和他的方法。你可以用IPropertyBag::Read 方法装载filter Properties Win32 VARIANT 变量中,然后初始化输入输出pin

下面的代码演示DirectShowVfWCapture filter如何执行IPersistPropertyBag::Load方法的。记住:在执行期间,你的filter必须提供一个有效的IPropertyBag指针。

STDMETHODIMP CVfwCapture::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog)
{
    HRESULT hr;
    CAutoLock cObjectLock(m_pLock);  // Locks the object; automatically unlocks it in the destructor.

    if (m_pStream)       // If the filter already exists for this stream
        return E_UNEXPECTED;

    VARIANT var;         // VARIANT from Platform SDK
    var.vt = VT_I4;      // four-byte integer (long)
    hr = pPropBag->Read(L"VFWIndex", &var, 0); // VFWIndex is the private name used by the Vidcap Class Manager to refer to the VFW Capture filter
    if(SUCCEEDED(hr))    // If it read the properties successfully
    {
        hr = S_OK;       // Defaults return value to S_OK
        m_iVideoId = var.lVal;   // Stores the specified hardware device number
        CreatePins(&hr);    // Inits the pins, replacing the return value if necessary
    }
    return hr;     // Returns S_OK or an error value, if CreatePins failed
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多