<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    DirectShow建立一個視頻捕捉程序

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    DirectShow 提供了用應用程序從適當的硬件中捕捉和預覽音/視頻的能力。數據源包括:VCR,camera,TV tuner,microphone,或其他的數據源。一個應用程序可以立刻顯示捕捉的數據(預覽),或是保存到一個文件中。 
    在這個例子中,ICaptureGraphBuilder 接口是處理捕捉工作的主要接口。你可以在你自己的捕捉程序中使用同樣的方法和接口。在這里主要討論ICaptureGraphBuilder 如何執行音/視頻的捕捉。我們假設你已經熟悉了DirectShow的filter graph的體系和一般的capture filter graph的結構(可以參考DirectShow基礎指南)。

     

     

    ICaptureGraphBuilder 接口提供了一個filter graph builder對象,讓你的應用程序在建立 capture filter graph時,省去處理很多單調乏味的工作,集中精力于捕捉中。他提供的方法滿足了基本的捕捉和預覽功能的要求。

     

    方法FindInterface ,在filter graph中查找一個于捕捉有關的詳細的接口。使的你可以訪問一個詳細接口的功能,而不需要你去列舉在filter graph中的pins 和 filters。
    方法RenderStream ,連接源過濾器和渲染過濾器,選擇添加一些中間的過濾器。
    方法ControlStream ,獨立的精確的控制graph的開始和結束幀。

     

    既然是硬件捕捉,當然要和硬件打交道,接著介紹設備列舉和捕捉接口。
    通過ICreateDevEnum::CreateClassEnumerator方法列舉捕捉系統中的設備。之后,實例化一個DirectShow的 filter去使用這個設備。接著用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 函數如下:
    /*
     * Put up a dialog to allow the user to select a capture file.
     */
    BOOL SetCaptureFile(HWND hWnd)
    {
        if (OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH))
        {
            OFSTRUCT os;
            // We have a capture file name
            /*
             * if this is a new file, then invite the user to
             * allocate some space
            */
            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;

     

        /*
         * show the allocate file space dialog to encourage
         * the user to pre-allocate space
         */
        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 writer。DirectShow 提供了一個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 filter的preview pin到video 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 window的handle,設置窗口屬性,或把他放到想要的位置。

     

    // 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 graph的streams的開始和結束時間。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_TIME在DirectShow中定義為最大的參考時間。在這里意味著忽略或取消指定的操作。
    最后的參數,wStartCookie和wStopCookie分別是開始和結束的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_TIME,NULL為結束時間。
    下面的例子告訴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把捕捉的數據保存到指定的文件中。

     

    /*
     * Put up a dialog to allow the user to save the contents of the capture file
     * elsewhere
     */
    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 。

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全