65.9K
CodeProject 正在变化。 阅读更多。
Home

使用不同的 ISampleGrabber 模式从流式 URL 捕获图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (19投票s)

2009年10月12日

CPOL

4分钟阅读

viewsIcon

67080

downloadIcon

1892

本文介绍了如何使用不同的 ISampleGrabber 模式从流 URL 捕获图像。

引言

本文解释了如何从流 URL(例如 WME 的流)捕获帧并使用 DirectShow 将其保存为 BMP。在本文中,我使用了 WMASF Reader(网络读取器)的 IBase 过滤器来读取网络资源,并使用 ISampleGrabber 过滤器捕获位图帧。在这里,我尝试解释使用 ISampleGrabber 过滤器捕获位图的两种方法(SetCallBackGetCurrentBuffer)。

背景

在我最近的一个项目中,我遇到了同样的问题,即从 WME 流 URL 捕获快照。我在网上搜索了一下,看到了很多关于从电影文件或其他内容捕获图像的文章,但没有关于如何从流 URL 捕获图像的文章。我借助 MSDN 和网络上的一些参考资料实现了这一点。最初,我通过使用 IFileSourceFilterISampleGrabber 接口中的 GetCurrentBuffer 方法来实现了一个解决方案。这对我来说已经足够了。之后,我尝试了 ISampleGrabber 接口中的 CallBack (SetCallBack) 以获得不同的体验。我在文中解释了这两种模式。我将尝试解释我学到和实现的东西。我希望示例代码能帮助解决您的问题。

设置您的 Visual Studio 项目

您需要将 Windows Platform SDK 和 DShow 基类的头文件添加到您的包含路径中。项目必须链接 Strmbase.lib

#include "qedit.h"   // SampleGrabber filter
#include "atlbase.h" // for using atl support
#include "dshow.h"   // DirectShow header

Using the Code

本文将使用以下 DShow 接口...

IGraphBuilder *pGraph = NULL; //Graph Manager 
IMediaControl *pControl = NULL; //Media Control for run the graph
IMediaEvent *pEvent = NULL; //Media Event interface for capture the media events
IBaseFilter *pWMASFReader = NULL; //IBase Filter for WM ASF READER (network reader) 
IPin *pStreamOut = NULL,*pStreamRender = NULL; //IPin interfaces for connecting filters 
IFileSourceFilter *pIFileSourceFilter = NULL; //for specifiying network target....

首先,初始化 COM 并创建过滤器图管理器。

HRESULT hr = CoInitialize(NULL); 

if(FAILED(hr)) 
    return FALSE ; 

hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, 
                      IID_IGraphBuilder, (void **)&pGraph); 

if(FAILED(hr)) 
    return FALSE;

创建用于网络读取的 WM ASF Reader 过滤器,并将其添加到图中。然后,查询文件源过滤器以指定网络位置,并使用 IFileSourceFilter::Load() 方法加载指定的网络位置(例如:http://host:port)。它将加载所需的网络源;如果找不到网络源或网络源无效,则会返回带有结果代码的 HRESULT 值。m_strURL 是一个 CString 变量,其中包含所需网络位置。

hr = CoCreateInstance(CLSID_WMAsfReader,NULL,CLSCTX_INPROC_SERVER, 
                      IID_IBaseFilter, (void **) &pWMASFReader); 

if (SUCCEEDED(hr)) 
{ 
    hr = pGraph->AddFilter(pWMASFReader,L"WM ASF Reader"); 

    if (SUCCEEDED(hr)) 
    { 
        hr = pWMASFReader->QueryInterface(IID_IFileSourceFilter, 
                                         (void **) &pIFileSourceFilter); 

        if (SUCCEEDED(hr)) 
        { 
            // load network source 
            hr = pIFileSourceFilter->Load(m_strURL.AllocSysString(), NULL); 
        } 
    } 
} 
if(FAILED(hr))
   return FALSE;

现在我们已经有了网络源。我们需要创建 Sample Grabber 过滤器并获取 Sample Grabber 接口。创建 Sample Grabber 过滤器,将其添加到图中,并查询 ISampleGrabberISampleGrabber *m_pGrabber)。

// Create the Sample Grabber.
IBaseFilter *pGrabberF = NULL;
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
                      IID_IBaseFilter, (void**)&pGrabberF);
if (FAILED(hr))
{
    // Return an error.
    return FALSE;
}
hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
if (FAILED(hr))
{
    // Return an error.
    return FALSE;
}

pGrabberF->QueryInterface(IID_ISampleGrabber, (void**)&m_pGrabber);

我们需要为 Sample Grabber 输入引脚上的连接指定媒体类型。为此,我们将使用 AM_MEDIA_TYPE 结构。

AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
hr = m_pGrabber->SetMediaType(&mt);

如果需要,您可以向图中添加一个 null renderer 过滤器,以防止显示预览窗口。null renderer 过滤器是一个丢弃它接收到的每个样本的渲染器,而不显示或渲染样本数据。否则,您可以从图中查询 IVideoWindow 并处理弹出窗口。

IBaseFilter    *pNullRenderer;
hr = CoCreateInstance (CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
                       IID_IBaseFilter, (void **)&pNullRenderer);
hr = pGraph->AddFilter(pNullRenderer, L"Null Renderer");

现在我们将把网络读取器过滤器连接到 grabber 过滤器。Sample Grabber 是一个转换过滤器,因此输出引脚必须连接到另一个过滤器。通常,您可能只想在完成样本后丢弃它们。在这种情况下,将 Sample Grabber 连接到 null renderer 过滤器,后者会丢弃它接收到的数据。

hr = ConnectFilters(pGraph, pWMASFReader, pGrabberF);

以下函数将第一个过滤器连接到第二个过滤器

HRESULT CVideoImageCapDlg::ConnectFilters(IGraphBuilder *pGraph, 
        IBaseFilter *pFirst, IBaseFilter *pSecond)
{
     IPin *pOut = NULL, *pIn = NULL;
    HRESULT hr = GetPin(pSecond, PINDIR_INPUT, &pIn);
    if (FAILED(hr)) return hr;
    // The previous filter may have multiple outputs, so try each one!
    IEnumPins  *pEnum;
    pFirst->EnumPins(&pEnum);
    while(pEnum->Next(1, &pOut, 0) == S_OK)
    {
            PIN_DIRECTION PinDirThis;
            pOut->QueryDirection(&PinDirThis);
            if (PINDIR_OUTPUT == PinDirThis)
            {
                hr = pGraph->Connect(pOut, pIn);
                if(!FAILED(hr))
                {
                       break;
                }
            }
            pOut->Release();
    }
    pEnum->Release();
    pIn->Release();
    pOut->Release();
    return hr;
}

然后,获取输出引脚并渲染流。

IPin * pGrabOutPin=NULL;
hr= GetPin( pGrabberF,PINDIR_OUTPUT,&pGrabOutPin);

HRESULT CVideoImageCapDlg::GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin)
{
    IEnumPins  *pEnum;
    IPin       *pPin;
    pFilter->EnumPins(&pEnum);
    while(pEnum->Next(1, &pPin, 0) == S_OK)
    {
         PIN_DIRECTION PinDirThis;
         pPin->QueryDirection(&PinDirThis);
         if (PinDir == PinDirThis)
         {
                pEnum->Release();
                *ppPin = pPin;
                return S_OK;
         }
         pPin->Release();
    }
    pEnum->Release();
    return E_FAIL;  
}


hr = pGraph->Render(pGrabOutPin);
if( FAILED( hr ) )
{
    AfxMessageBox("Could not render grabber output pin\r\n");
    return FALSE;
}

现在查询 Media control 以运行图,并查询 Media event 以检索事件通知。

hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); 
if(FAILED(hr)) 
    return FALSE; 

hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); 
if(FAILED(hr)) 
return FALSE ;

Sample Grabber 以两种模式之一运行

  1. 缓冲模式在将样本传递到下游之前复制每个样本。
  2. 回调模式在每个样本上调用应用程序定义的函数。

调用 ISampleGrabber::SetOneShot 方法并将值设置为 FALSE。这会使 Sample Grabber 在接收到第一个媒体样本后继续运行图。然后,调用 ISampleGrabber::SetBufferSamples 并将值设置为 TRUE,以指定是否在样本数据通过过滤器时将其复制到缓冲区。这样,您就可以通过调用 ISampleGrabber::GetCurrentBuffer 来获取复制的缓冲区。

// Set one-shot mode false and  buffering.
hr = m_pGrabber->SetOneShot(FALSE);
hr = m_pGrabber->SetBufferSamples(TRUE);

如果一切顺利,我们就可以运行图了。

long evCode=0;

hr=pControl->Run(); // Run the graph.

hr=pEvent->WaitForCompletion(INFINITE, &evCode); // Wait till its done.
//please be carefull when using INFINITE, 
//this may block the entire app indefinitely.
//It will cause the application hang up.
Sleep(1000);

如果图正常运行,我们就可以使用 ISampleGrabber::GetCurrentBuffer() 方法检索当前缓冲区。SaveBufferToBitmap(mt) 函数将使用 ISampleGrabber::GetCurrentBuffer() 方法获取缓冲区并将其保存为 BMP。

BOOL CVideoImageCapDlg::SaveBufferToBitmap(AM_MEDIA_TYPE mt)
{
    // Find the required buffer size.
    long cbBuffer = 0;
    HRESULT hr = m_pGrabber->GetCurrentBuffer(&cbBuffer, NULL);

    switch(hr)
    {
        case E_INVALIDARG:
            AfxMessageBox("E_INVALIDARG");
            break;

        case E_OUTOFMEMORY:
            AfxMessageBox("E_OUTOFMEMORY");
            break;

        case E_POINTER:
            AfxMessageBox("E_POINTER");
            break;

        case VFW_E_NOT_CONNECTED:
            AfxMessageBox("VFW_E_NOT_CONNECTED");
            break;

        case VFW_E_WRONG_STATE:
            AfxMessageBox("VFW_E_WRONG_STATE");
            break;
    }

    if(FAILED(hr))
        return FALSE;

    char *pBuffer = new char[cbBuffer];
    if (!pBuffer) 
    {
        // Out of memory. Return an error code.
        return FALSE;
    }
    hr = m_pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);


    ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
    hr = m_pGrabber->GetConnectedMediaType(&mt);
    if (FAILED(hr))
    {
        // Return error code.
        return FALSE;
    }
    // Examine the format block.
    VIDEOINFOHEADER *pVih;
    if ((mt.formattype == FORMAT_VideoInfo) && 
        (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
        (mt.pbFormat != NULL) ) 
    {
        pVih = (VIDEOINFOHEADER*)mt.pbFormat;
    }
    else 
    {
        return FALSE;
    }

     //
     // Save image data as Bitmap.
     // This is just to make this sample easily understandable.
     //
     HANDLE fh;
     BITMAPFILEHEADER bmphdr;
     DWORD nWritten;

     memset(&bmphdr, 0, sizeof(bmphdr));

     bmphdr.bfType = ('M' << 8) | 'B';
     bmphdr.bfSize = sizeof(bmphdr) + sizeof(BITMAPINFOHEADER) + cbBuffer;
     bmphdr.bfOffBits = sizeof(bmphdr) + sizeof(BITMAPINFOHEADER);

     fh = CreateFile(m_strSaveTo,
     GENERIC_WRITE, 0, NULL,
     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
     WriteFile(fh, &bmphdr, sizeof(bmphdr), &nWritten, NULL);
     WriteFile(fh,
     &pVih->bmiHeader,
     sizeof(BITMAPINFOHEADER), &nWritten, NULL);
     WriteFile(fh, pBuffer, cbBuffer, &nWritten, NULL);
     CloseHandle(fh);

     free(pBuffer);

     return TRUE;
}

我们还可以通过设置 ISampleGrabber->SetCallback() 方法来调用回调函数来捕获媒体样本。FrameGrabCallback 是一个派生自 ISampleGrabberCB 的类。下面是一个回调类的示例。请注意,该类实现了 IUnknown,它通过 ISampleGrabber 接口继承,但它不维护引用计数。这是安全的,因为应用程序在堆栈上创建对象,并且对象在过滤器图的整个生命周期内都保持在其范围内。所有工作都在 BufferCB 方法中完成,该方法在 Sample Grabber 获得新样本时由其调用。在下面的示例中,该方法将位图写入文件。

//
// Callback class for Sample Grabber filter.
//
class FrameGrabCallback : public ISampleGrabberCB
{
public :

 // Fake out any COM ref counting
    //
  STDMETHODIMP_(ULONG) AddRef() { return 2; }
  STDMETHODIMP_(ULONG) Release() { return 1; }
  STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
  {
    if (NULL == ppv)
      return E_POINTER;
  
    *ppv = NULL;
    if (IID_IUnknown == iid)
    {
      *ppv = (IUnknown*)this;
      AddRef();
      return S_OK;
    }
    else 
    if (IID_ISampleGrabberCB == iid)
    {
      *ppv = (ISampleGrabberCB*)this;
      AddRef();
      return S_OK;
    }
  
    return E_NOINTERFACE;
  }
    
  // Constructor/destructor
  FrameGrabCallback() 
  {
      framenum=0;
  }
    
  ~FrameGrabCallback() {}

public:
  // These will get set by the main thread below. We need to
  // know this in order to write out the bmp
  long Width;
  long Height;
  long framenum;
  CString strPath;

  STDMETHODIMP SampleCB(double n,IMediaSample *pms)
  {
    return 0;
  }


    // The sample grabber is calling us back on its deliver thread.
    // This is NOT the main app thread!
    //
    STDMETHODIMP BufferCB( double SampleTime, BYTE * pBuffer, long BufferSize )
    {
        //
        // Convert the buffer into a bitmap
        // strPath ="path"; eg : "c://Test.bmp"

        TCHAR szFilename[MAX_PATH];
        if(strPath=="")
        {
            wsprintf(szFilename, TEXT("bitmap%ld.bmp\0"),  framenum );
        }
        else
        {
            wsprintf(szFilename, strPath ,  framenum );
        }
        
        
        framenum++;
        // Create a file to hold the bitmap
        HANDLE hf = CreateFile(szFilename, GENERIC_WRITE, FILE_SHARE_READ, 
                               NULL, CREATE_ALWAYS, NULL, NULL );

        if( hf == INVALID_HANDLE_VALUE )
        {
            _tprintf( TEXT("INVALID_HANDLE_VALUE\r\n"));
        }

        /*_tprintf(TEXT("Found a sample at time %ld ms\t[%s]\r\n"), 
                 long(SampleTime*1000), szFilename );*/

        // Write out the file header
        //
        BITMAPFILEHEADER bfh;
        memset( &bfh, 0, sizeof( bfh ) );
        bfh.bfType = 'MB';
        bfh.bfSize = sizeof( bfh ) + BufferSize + sizeof( BITMAPINFOHEADER );
        bfh.bfOffBits = sizeof( BITMAPINFOHEADER ) + sizeof( BITMAPFILEHEADER );

        DWORD Written = 0;
        WriteFile( hf, &bfh, sizeof( bfh ), &Written, NULL );

        // Write the bitmap format
        //
        BITMAPINFOHEADER bih;
        memset( &bih, 0, sizeof( bih ) );
        bih.biSize = sizeof( bih );
        bih.biWidth = Width;
        bih.biHeight = Height;
        bih.biPlanes = 1;
        bih.biBitCount = 24;

        Written = 0;
        WriteFile( hf, &bih, sizeof( bih ), &Written, NULL );

        // Write the bitmap bits
        //
        Written = 0;
        WriteFile( hf, pBuffer, BufferSize, &Written, NULL );

        CloseHandle( hf );

        return 0;
    }
};

创建此类后,请在运行图之前使用以下代码。一次只使用两种方法之一(回调或获取当前缓冲区)。

// Set the callback, so we can grab the one sample
//Also set the video width and height to grab the frame.

FrameGrabCallback   m_FrameGrabCallback;

m_FrameGrabCallback.Width=320;
m_FrameGrabCallback.Height=240;
m_FrameGrabCallback.framenum=1;

hr = m_pGrabber->SetCallback( &m_FrameGrabCallback, 1 );//Set the callback method

完成所有工作后,我们可以停止图,取消回调函数的订阅,并释放所有资源。

m_pGrabber->SetCallback(NULL,1);
pControl->Stop();
pControl->Release();
m_pGrabber->Release();
pEvent->Release();
pGraph->Release();
pGrabberF->Release();

参考

历史

  • First version.
© . All rights reserved.