AMCAP程序详解

来源:互联网 发布:outlook windows 安全 编辑:程序博客网 时间:2024/05/16 04:50

AMCAP程序详解

 
AMCAP程序详解

文章原始出处 CSDN 作者 capboy

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

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

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

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

    既然是硬件捕捉,当然要和硬件打交道,接着介绍设备列举和捕捉接口。

    通过ICreateDevEnum::CreateClassEnumerator方法列举捕捉系统中的设备。之后,实例化一个DirectShow的filter去使用这个设备。
 用ICaptureGraphBuilder::FindInterface去获得与捕捉相关的接口指针IAMDroppedFrames,IAMVideoCompression, IAMStreamConfig,以及 IAMVfwCaptureDialogs。因为设备列举和捕捉接口比较长,放在这会打乱结构,所有专门写了一篇(参考设备列举和捕捉接口)。

NOTE:
   这个示例是DirectShow自带的例子。你可以在DirectShowSDK的目录Sample\DS\Caputre看这个例子代码(AMCap.cpp)。这里只是他的一些片断代码。可以说是他的中文模块的说明。AMCap例子中,把所有的接口指针和一些成员变量保存在一个全局结构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;

设置文件名

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

SetCaptureFile 和 AllocCaptureFile 函数如下:


BOOL SetCaptureFile(HWND hWnd)
{
    if(OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH))
    {
        OFSTRUCTos;

        //We have a capture file name

        
        if(OpenFile(gcap.szCaptureFile, &os, OF_EXIST) ==HFILE_ERROR)
       {
             //Bring up dialog, and set new file size
             BOOLf = AllocCaptureFile(hWnd);
             if(!f)
                    returnFALSE;
        }
    }
    else{
        returnFALSE;
    }

    SetAppCaption();// need a new app caption

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

// Preallocate the capturefile
//

BOOL AllocCaptureFile(HWND hWnd)
{
// We'll get into an infinite loop in the dlgproc setting a value
if (gcap.szCaptureFile[0] == 0)
    returnFALSE;


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
    BOOLf = MakeBuilder();
    if(!f)
        returnFALSE;
    WCHARwach[_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 filespace",MB_OK | MB_ICONEXCLAMATION);
       return FALSE;
    }
    returnTRUE;
}
else {
    returnFALSE;
}
}

建立GraphBuilder对象

    AMCap的MakeBuilder函数建立了一个capturegraph builer对象,通过调用 CoCreateInstance获得了ICaptureGraphBuilder接口指针。AMCap把他存储到gcap结构的pBuilder中。

// Make a graph builderobject we can use for capture graph building
//

BOOL MakeBuilder()
{
    //We have one already
    if(gcap.pBuilder)
    returnTRUE;

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

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

    这包括一个multiplexerfilter 和 file writer。DirectShow 提供了一个AVIMUX(multiplexer)filter。
    在这里ICaptureGraphBuilder::SetOutputFileName是一个关键的方法。它把multiplexer 和 file writer添加到filtergraph中,连接他们,并设置文件的名字。第一个参数MEDIASUBTYPE_Avi,指出capture graph builder对象将插入一个AVI multiplexer filter,因此,filewriter将以AVI文件格式记录捕捉的数据。第二个参数(wach)是文件名。最后的两个参数指出multiplexer filter(gcap.pRender) 和file writer filter(gcap.pSink),这两个是通过SetOutputFileName函数初始化的。AMCap存储这些指针到全局结构gcap中。capture graph builder 对象建立了一个filtergraph对象(IGraphBuilder),把这两个filter加入到filter graph中去。他告诉filewriter使用指定的文件保存数据。下面的例子演示了如何调用SetOutputFileName。

//
// We need a rendering section that will write the capture file outin 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);
    gotoSetupCaptureFail;
}

获得当前的FilterGraph

    因为在调用SetOutputFileName中,capturegraph builder 对象建立了一个filter graph,所以你必须把需要的filter加入同一个filter graph中。通过ICaptureGraphBuilder::GetFiltergraph获得新建立的filtergraph。返回的指针是参数gcap.pFg。

//
// The graph builder created a filter graph to do that. Find outwhat 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);
    gotoSetupCaptureFail;
}

添加音/视频过滤器到当前的Filter Graph

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

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

渲染视频捕捉过滤器的CapturePin和音频捕捉的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 havepreview, so
// don't worry if it doesn't work
//

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

//
// Render the audio capture pin?
//

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

渲染Video CaptureFilter的 Preview Pin

    再次调用ICaptureGraphBuilder::RenderStream,从capture filter的previewpin到video renderer。代码如下:

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

获得访问Video PreviewWindow的接口指针

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

// This will go through apossible decoder, find the video renderer it's
// connected to, and get the IVideoWindow interface onit

hr =gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,gcap.pVCap, IID_IVideoWindow, (void**)&gcap.pVW);
if (hr != NOERROR) {
    ErrMsg("Thisgraph cannot preview");
}
else {
    RECTrc;
    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 baris
    GetClientRect(ghwndApp,&rc);
    cyBorder= GetSystemMetrics(SM_CYBORDER);
    cy= statusGetHeight() + cyBorder;
    rc.bottom-= cy;
    gcap.pVW->SetWindowPosition(0,0, rc.right, rc.bottom); // be thisbig
    gcap.pVW->put_Visible(OATRUE);
}

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

控制 Capture FilterGraph

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

注意:不是所有的capture filter都可以,因为不是每一个capturefilter都支持IAMStreamControl。

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

// Let the preview sectionrun, 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 capturequite 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_TIME,NULL为结束时间。
    下面的例子告诉filtergraph开始预览(第三个参数:开始时间为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);
    returnFALSE;

    如果graph已经运行,通过调用ICaptureGraphBuilder::ControlStream立刻开始捕捉。例如下面的代码,控制整个的filtergraph(第二个参数为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);

   通过普通的OpenFile Dialog得到新的文件名。用 MultiByteToWideChar函数把文件名转成宽字符(widestring),使用ICaptureGraphBuilder::CopyCaptureFile把捕捉的数据保存到指定的文件中。


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

if (gcap.pBuilder == NULL)
    returnFALSE;

if (OpenFileDialog(hWnd, achDstFile, _MAX_PATH)){

// We have a capture filename
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 - pleasewait...");

// We need our own graphbuilder 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 orsomething
}
}

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

0 0
原创粉丝点击