Grabbing Media Samples
This article describes how to retrieve media samples, using the Sample Grabber filter.
The Microsoft® DirectShow® architecture shields applications from having to manipulate media data directly. If you need to modify media data, you should write a transform filter or a Microsoft® DirectX® Media Object (DMO) for this purpose. On the other hand, if you need to use the data without modification—for example, to display a frame from a video file—you can use the Sample Grabber filter.
This article contains the following sections:
Adding the Sample Grabber to the Filter Graph
Buffers and One-Shot Mode
Using a Callback Method
Adding the Sample Grabber to the Filter Graph
The Sample Grabber is a transform filter that supports the ISampleGrabber interface. It passes all samples downstream unchanged, so it can be inserted into a filter graph without altering the data stream. You can then use it to grab samples as they pass through the filter.
By default, the Sample Grabber has no preferred media types. Before you insert it into a filter graph, set the media type for the input pin by calling the ISampleGrabber::SetMediaType method. Setting a media type ensures that the filter graph manager inserts the Sample Grabber at the correct point in the graph.
The SetMediaType method takes a pointer to an AM_MEDIA_TYPE structure that describes the media type. Depending on the situation, you can set the subtype or format type to GUID_NULL, which indicates "unspecified." The following example adds the Sample Grabber to the filter graph and sets the media type to 24-bit uncompressed RGB video:
#include <dshow.h>
#include <qedit.h>
IBaseFilter *pF = NULL;
ISampleGrabber *pGrab = NULL; // Be sure to release these later.
AM_MEDIA_TYPE mt;
CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (LPVOID *)&pF);
pF->QueryInterface(IID_ISampleGrabber, (void **)&pGrab);
pGraph->AddFilter(pF, L"Grabber");
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
mt.formattype = FORMAT_VideoInfo;
hr = pGrab->SetMediaType(&mt);
Now you can build the rest of the graph and connect the Sample Grabber filter. There are various ways to do this, including:
Call IGraphBuilder::RenderFile. The filter graph manager will connect the Sample Grabber according to its media type.
Add filters one at a time and connect them with the IGraphBuilder::Connect method.
Use the ICaptureGraphBuilder2 interface.
For example, the following code builds a playback graph for the file Example.avi:
pGraph->RenderFile(L"C://Example.avi", NULL);
Because the Sample Grabber's media type was set to uncompressed video, the filter graph manager puts it between the video decompressor and the video renderer, where it will receive uncompressed video samples.
If you want to grab samples without rendering them, connect the Sample Grabber's output pin to the Null Renderer filter, which discards incoming samples.
Buffers and One-Shot Mode
The Sample Grabber can copy the samples that it receives to an internal buffer. To enable buffering, call the ISampleGrabber::SetBufferSamples method with a value of TRUE. To retrieve a copy of the sample, call the ISampleGrabber::GetCurrentBuffer method.
Each sample overwrites the previous one in the buffer. To grab a sample from a particular point in the stream, switch the Sample Grabber to "one-shot" mode. In one-shot mode, the Sample Grabber stops the graph as soon as it receives one sample. To enable one-shot mode, call the ISampleGrabber::SetOneShot method with a value of TRUE.
Seek to the desired time, run the graph, and wait for the graph to stop. The following code shows how to do this:
// Query the filter graph manager for these interfaces (not shown).
IMediaControl *pMediaControl = NULL;
IMediaSeeking *pSeek = NULL;
IMediaEvent *pEvent = NULL;
// Set up one-shot mode.
pGrab->SetBufferSamples(TRUE);
pGrab->SetOneShot(TRUE);
// Seek three seconds.
REFERENCE_TIME rtStart = 3 * 10000000;
REFERENCE_TIME rtStop = rtStart;
hr = pSeek->SetPositions(&rtStart, AM_SEEKING_AbsolutePositioning,
&rtStop, AM_SEEKING_AbsolutePositioning);
// Run the graph and wait for completion.
long evCode;
hr = pMediaControl->Run();
hr = pEvent->WaitForCompletion(INFINITE, &evCode);
When the WaitForCompletion method returns, the Sample Grabber's buffer holds a copy of the video frame. The buffer contains only the media data portion of the sample; it does not include the format header. To obtain the format header, call ISampleGrabber::GetConnectedMediaType. This method returns an AM_MEDIA_TYPE structure whose pbFormat member points to the format header. Be sure to release the buffer memory when you're done using it.
To illustrate, the following code creates a device-independent bitmap (DIB) from a video stream, using the format header and the sample buffer:
AM_MEDIA_TYPE MediaType;
pGrab->GetConnectedMediaType(&MediaType);
// Get a pointer to the video header.
VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)MediaType.pbFormat;
// The video header contains the bitmap information.
// Copy it into a BITMAPINFO structure.
BITMAPINFO BitmapInfo;
ZeroMemory(&BitmapInfo, sizeof(BitmapInfo));
CopyMemory(&BitmapInfo.bmiHeader, &(pVideoHeader->bmiHeader), sizeof(BITMAPINFOHEADER));
// Create a DIB from the bitmap header, and get a pointer to the buffer.
void *buffer = NULL;
HBITMAP hBitmap =
CreateDIBSection(0, &BitmapInfo, DIB_RGB_COLORS, &buffer, NULL, 0);
// Copy the image into the buffer.
hr = pGrab->GetCurrentBuffer(NULL, (long *)buffer);
The application can call BitBlt to display the image. For more information, see the Platform SDK. Although this example assumes a video stream, the Sample Grabber can retrieve audio samples or any other media sample. To blit the image, use code such as the following:
long Width = pVideoHeader->bmiHeader.biWidth;
long Height = pVideoHeader->bmiHeader.biHeight;
HDC hdcDest = GetDC(hwnd);
HDC hdcSrc = CreateCompatibleDC(NULL);
SelectObject(hdcSrc, hBitmap);
BitBlt(hdcDest, 0, 0, Width, Height, hdcSrc, 0, 0, SRCCOPY);
Using a Callback Method
Instead of working in one-shot mode, the Sample Grabber can invoke a callback method for each sample that it receives. The application must implement the ISampleGrabberCB interface. It contains the following methods:
SampleCB: Receives a pointer to the sample's IMediaSample interface.
BufferCB: Receives a buffer that contains the media data.
Typically, you would implement only one of these methods. To set the callback, call the ISampleGrabber::SetCallback method. It takes an index value that specifies which callback method to use. If you specify the BufferCB method, also call ISampleGrabber::SetBufferSamples to enable buffering, as described in the preceding section.
The following example implements the callback interface in a class named CGrabCB. The class derives from the CUnknown class, one of the base classes included with the DirectShow SDK. Using CUnknown is not required, but it makes the code somewhat shorter. For more information on using CUnknown, see How to Implement IUnknown.
In this example, the SampleCB method prints the start time of each sample in the console window. The BufferCB method is not implemented and returns E_NOTIMPL.
#include <streams.h>
// Link to Strmbase.lib
class CGrabCB: public CUnknown, public ISampleGrabberCB
{
public:
DECLARE_IUNKNOWN;
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
if( riid == IID_ISampleGrabberCB )
{
return GetInterface((ISampleGrabberCB*)this, ppv);
}
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}
// ISampleGrabberCB methods
STDMETHODIMP SampleCB(double SampleTime, IMediaSample *pSample)
{
printf(" Sample time: %f/n", SampleTime);
return S_OK;
}
STDMETHODIMP BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen)
{
return E_NOTIMPL;
}
// Constructor
CGrabCB( ) : CUnknown("SGCB", NULL)
{ }
};
To use the callback, create a new instance of the CGrabCB class and pass it to the ISampleGrabber::SetCallback method. Then run the filter graph:
pGrab->SetOneShot(FALSE);
pGrab->SetBufferSamples(FALSE);
CGrabCB *cb = new CGrabCB();
pGrab->SetCallback(cb, 0);
This article describes how to retrieve media samples, using the Sample Grabber filter.
The Microsoft® DirectShow® architecture shields applications from having to manipulate media data directly. If you need to modify media data, you should write a transform filter or a Microsoft® DirectX® Media Object (DMO) for this purpose. On the other hand, if you need to use the data without modification—for example, to display a frame from a video file—you can use the Sample Grabber filter.
This article contains the following sections:
Adding the Sample Grabber to the Filter Graph
Buffers and One-Shot Mode
Using a Callback Method
Adding the Sample Grabber to the Filter Graph
The Sample Grabber is a transform filter that supports the ISampleGrabber interface. It passes all samples downstream unchanged, so it can be inserted into a filter graph without altering the data stream. You can then use it to grab samples as they pass through the filter.
By default, the Sample Grabber has no preferred media types. Before you insert it into a filter graph, set the media type for the input pin by calling the ISampleGrabber::SetMediaType method. Setting a media type ensures that the filter graph manager inserts the Sample Grabber at the correct point in the graph.
The SetMediaType method takes a pointer to an AM_MEDIA_TYPE structure that describes the media type. Depending on the situation, you can set the subtype or format type to GUID_NULL, which indicates "unspecified." The following example adds the Sample Grabber to the filter graph and sets the media type to 24-bit uncompressed RGB video:
#include <dshow.h>
#include <qedit.h>
IBaseFilter *pF = NULL;
ISampleGrabber *pGrab = NULL; // Be sure to release these later.
AM_MEDIA_TYPE mt;
CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (LPVOID *)&pF);
pF->QueryInterface(IID_ISampleGrabber, (void **)&pGrab);
pGraph->AddFilter(pF, L"Grabber");
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
mt.formattype = FORMAT_VideoInfo;
hr = pGrab->SetMediaType(&mt);
Now you can build the rest of the graph and connect the Sample Grabber filter. There are various ways to do this, including:
Call IGraphBuilder::RenderFile. The filter graph manager will connect the Sample Grabber according to its media type.
Add filters one at a time and connect them with the IGraphBuilder::Connect method.
Use the ICaptureGraphBuilder2 interface.
For example, the following code builds a playback graph for the file Example.avi:
pGraph->RenderFile(L"C://Example.avi", NULL);
Because the Sample Grabber's media type was set to uncompressed video, the filter graph manager puts it between the video decompressor and the video renderer, where it will receive uncompressed video samples.
If you want to grab samples without rendering them, connect the Sample Grabber's output pin to the Null Renderer filter, which discards incoming samples.
Buffers and One-Shot Mode
The Sample Grabber can copy the samples that it receives to an internal buffer. To enable buffering, call the ISampleGrabber::SetBufferSamples method with a value of TRUE. To retrieve a copy of the sample, call the ISampleGrabber::GetCurrentBuffer method.
Each sample overwrites the previous one in the buffer. To grab a sample from a particular point in the stream, switch the Sample Grabber to "one-shot" mode. In one-shot mode, the Sample Grabber stops the graph as soon as it receives one sample. To enable one-shot mode, call the ISampleGrabber::SetOneShot method with a value of TRUE.
Seek to the desired time, run the graph, and wait for the graph to stop. The following code shows how to do this:
// Query the filter graph manager for these interfaces (not shown).
IMediaControl *pMediaControl = NULL;
IMediaSeeking *pSeek = NULL;
IMediaEvent *pEvent = NULL;
// Set up one-shot mode.
pGrab->SetBufferSamples(TRUE);
pGrab->SetOneShot(TRUE);
// Seek three seconds.
REFERENCE_TIME rtStart = 3 * 10000000;
REFERENCE_TIME rtStop = rtStart;
hr = pSeek->SetPositions(&rtStart, AM_SEEKING_AbsolutePositioning,
&rtStop, AM_SEEKING_AbsolutePositioning);
// Run the graph and wait for completion.
long evCode;
hr = pMediaControl->Run();
hr = pEvent->WaitForCompletion(INFINITE, &evCode);
When the WaitForCompletion method returns, the Sample Grabber's buffer holds a copy of the video frame. The buffer contains only the media data portion of the sample; it does not include the format header. To obtain the format header, call ISampleGrabber::GetConnectedMediaType. This method returns an AM_MEDIA_TYPE structure whose pbFormat member points to the format header. Be sure to release the buffer memory when you're done using it.
To illustrate, the following code creates a device-independent bitmap (DIB) from a video stream, using the format header and the sample buffer:
AM_MEDIA_TYPE MediaType;
pGrab->GetConnectedMediaType(&MediaType);
// Get a pointer to the video header.
VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)MediaType.pbFormat;
// The video header contains the bitmap information.
// Copy it into a BITMAPINFO structure.
BITMAPINFO BitmapInfo;
ZeroMemory(&BitmapInfo, sizeof(BitmapInfo));
CopyMemory(&BitmapInfo.bmiHeader, &(pVideoHeader->bmiHeader), sizeof(BITMAPINFOHEADER));
// Create a DIB from the bitmap header, and get a pointer to the buffer.
void *buffer = NULL;
HBITMAP hBitmap =
CreateDIBSection(0, &BitmapInfo, DIB_RGB_COLORS, &buffer, NULL, 0);
// Copy the image into the buffer.
hr = pGrab->GetCurrentBuffer(NULL, (long *)buffer);
The application can call BitBlt to display the image. For more information, see the Platform SDK. Although this example assumes a video stream, the Sample Grabber can retrieve audio samples or any other media sample. To blit the image, use code such as the following:
long Width = pVideoHeader->bmiHeader.biWidth;
long Height = pVideoHeader->bmiHeader.biHeight;
HDC hdcDest = GetDC(hwnd);
HDC hdcSrc = CreateCompatibleDC(NULL);
SelectObject(hdcSrc, hBitmap);
BitBlt(hdcDest, 0, 0, Width, Height, hdcSrc, 0, 0, SRCCOPY);
Using a Callback Method
Instead of working in one-shot mode, the Sample Grabber can invoke a callback method for each sample that it receives. The application must implement the ISampleGrabberCB interface. It contains the following methods:
SampleCB: Receives a pointer to the sample's IMediaSample interface.
BufferCB: Receives a buffer that contains the media data.
Typically, you would implement only one of these methods. To set the callback, call the ISampleGrabber::SetCallback method. It takes an index value that specifies which callback method to use. If you specify the BufferCB method, also call ISampleGrabber::SetBufferSamples to enable buffering, as described in the preceding section.
The following example implements the callback interface in a class named CGrabCB. The class derives from the CUnknown class, one of the base classes included with the DirectShow SDK. Using CUnknown is not required, but it makes the code somewhat shorter. For more information on using CUnknown, see How to Implement IUnknown.
In this example, the SampleCB method prints the start time of each sample in the console window. The BufferCB method is not implemented and returns E_NOTIMPL.
#include <streams.h>
// Link to Strmbase.lib
class CGrabCB: public CUnknown, public ISampleGrabberCB
{
public:
DECLARE_IUNKNOWN;
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
if( riid == IID_ISampleGrabberCB )
{
return GetInterface((ISampleGrabberCB*)this, ppv);
}
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}
// ISampleGrabberCB methods
STDMETHODIMP SampleCB(double SampleTime, IMediaSample *pSample)
{
printf(" Sample time: %f/n", SampleTime);
return S_OK;
}
STDMETHODIMP BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen)
{
return E_NOTIMPL;
}
// Constructor
CGrabCB( ) : CUnknown("SGCB", NULL)
{ }
};
To use the callback, create a new instance of the CGrabCB class and pass it to the ISampleGrabber::SetCallback method. Then run the filter graph:
pGrab->SetOneShot(FALSE);
pGrab->SetBufferSamples(FALSE);
CGrabCB *cb = new CGrabCB();
pGrab->SetCallback(cb, 0);