Using the Sample Grabber

 
Microsoft DirectShow 9.0

Using the Sample Grabber

The Sample Grabber filter is a transform filter that can be used to grab media samples from a stream as they pass through the filter graph.

If you simply want to grab a bitmap from a video file, it is easier to use the Media Detector (MediaDet) object. See Grabbing a Poster Frame for details. The Sample Grabber is more flexible, however, because it works with nearly any media type (see ISampleGrabber::SetMediaType for the few exceptions), and offers more control to the application.

Add the Sample Grabber to the Filter Graph

Start by creating an instance of the Sample Grabber filter and adding it to the filter graph:

// 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.
}
hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
if (FAILED(hr)
{
    // Return an error.
}

Query the Sample Grabber for the ISampleGrabber interface.

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

Set the Media Type

When you first create the Sample Grabber, it has no preferred media type. This means you can connect to almost any filter in the graph, but you would have no control over the type of data that it received. Before building the rest of the graph, therefore, you must set a media type for the Sample Grabber, by calling the ISampleGrabber::SetMediaType method.

When the Sample Grabber connects, it will compare this media type against the media type offered by the other filter. The only fields that it checks are the major type, subtype, and format type. For any of these, the value GUID_NULL means "accept any value." Most of the time, you will want to set the major type and subtype. For example, the following code specifies uncompressed 24-bit RGB video:

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

The next example sets the media type based on the bit depth of the display:

// Find the current bit depth.
HDC hdc = GetDC(NULL);
int iBitDepth = GetDeviceCaps(hdc, BITSPIXEL);
ReleaseDC(NULL, hdc);

// Set the media type.
mt.majortype = MEDIATYPE_Video;
switch (iBitDepth)
{
case 8:
    mt.subtype = MEDIASUBTYPE_RGB8;
    break;
case 16:
    mt.subtype = MEDIASUBTYPE_RGB555;
    break;
case 24:
    mt.subtype = MEDIASUBTYPE_RGB24;
    break;
case 32:
    mt.subtype = MEDIASUBTYPE_RGB32;
    break;
default:
    return E_FAIL;
}
hr = pGrabber->SetMediaType(&mt);

Build the Filter Graph

Now you can build the rest of the filter graph. Because the Sample Grabber will only connect using the media type you have specified, this lets you take advantage of the Filter Graph Manager's Intelligent Connect mechanisms when you build the graph.

For example, if you specified uncompressed video, you can connect a source filter to the Sample Grabber, and the Filter Graph Manager will automatically add the file parser and the decoder. The following example uses the ConnectFilters helper function, which is listed in Connect Two Filters:

IBaseFilter *pSrc;
hr = pGraph->AddSourceFilter(wszFileName, L"Source", &pSrc);
if (FAILED(hr))
{
    // Return an error code.
}
hr = ConnectFilters(pGraph, pSrc, pGrabberF);

The Sample Grabber is a transform filter, so the output pin must be connected to another filter. Often, you may simply want to discard the samples after you are done with them. In that case, connect the Sample Grabber to the Null Renderer Filter, which discards the data that it receives.

Be aware that placing the Sample Grabber between a video decoder and the video renderer can significantly hurt rendering performance. The Sample Grabber is a trans-in-place filter, which means the output buffer is the same as the input buffer. For video rendering, the output buffer is likely to be located on the graphics card, where read operations are much slower, compared with read operations in main memory.

Run the Graph

The Sample Grabber operates in one of two modes:

  • Buffering mode makes a copy of each sample before delivering the sample downstream.
  • Callback mode invokes an application-defined callback function on each sample.

This article describes buffering mode. (Before using callback mode, be aware that the callback function must be quite limited. Otherwise, it can drastically reduce performance or even cause deadlocks. For more information, see ISampleGrabber::SetCallback.) To activate buffering mode, call the ISampleGrabber::SetBufferSamples method with the value TRUE.

Optionally, call the ISampleGrabber::SetOneShot method with the value TRUE. This causes the Sample Grabber to stop the graph after it receives the first media sample, which is useful if you want to grab a single frame from the stream. Seek to the desired time, run the graph, and wait for the EC_COMPLETE event. Note that the level of frame accuracy depends on the source. For example, seeking an MPEG file is often not frame accurate.

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

pControl->Run(); // Run the graph.
pEvent->WaitForCompletion(INFINITE, &evCode); // Wait till it's done.

To run the graph as fast as possible, turn of the graph clock as described in Setting the Graph Clock.

Grab the Sample

In buffering mode, the Sample Grabber stores a copy of every sample. The ISampleGrabber::GetCurrentBuffer method copies the buffer into a caller-allocated array. To determine the size of the array that is needed, first call GetCurrentBuffer with a NULL pointer for the array address:

// Find the required buffer size.
long cbBuffer = 0;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);

Allocate the array and call the method a second time to copy the buffer:

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

The ISampleGrabber::GetConnectedMediaType method returns the format of the buffer:

AM_MEDIA_TYPE mt;
hr = pGrabber->GetConnectedMediaType(&mt);
if (FAILED(hr))
{
    // Return error code.
}
// Examine the format block.
VIDEOINFOHEADER *pVih;
if ((mt.formattype == FORMAT_VideoInfo) && 
    (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
    (mt.pbFormat != NULL) ) 
{
    pVih = (VIDEOINFOHEADER*)mt.pbFormat;
}
else 
{
    // Wrong format. Free the format block and return an error.
    FreeMediaType(mt);
    return VFW_E_INVALIDMEDIATYPE; 
}
// You can use the media type to access the BITMAPINFOHEADER information.
// For example, the following code draws the bitmap using GDI:
SetDIBitsToDevice(
    hdc, 0, 0, 
    pVih->bmiHeader.biWidth,
    pVih->bmiHeader.biHeight,
    0, 0, 
    0,
    pVih->bmiHeader.biHeight,
    pBuffer,
    (BITMAPINFO*)&pVih->bmiHeader,
    DIB_RGB_COLORS
);

// Free the format block when you are done:
FreeMediaType(mt);