Microsoft DirectX 8.0

How to Write a Capture Application

This topic describes how to write a video capture application using Microsoft® DirectShow®. It contains the following topics:

Overview of Capture in DirectShow

The term capture can mean writing to a file, but also includes preview or any other use of captured data. Capture is not limited to video cameras—for example, a TV tuner card is a capture device. This topic discusses audio and video capture, but keep in mind that capture data might include closed-captioned or vertical blanking interval (VBI) data.

Capture Graphs

DirectShow handles capture as it does any other task: through filter graphs. This type of graph is called a capture graph. Because of the variety of hardware devices and data formats available, there are many ways to configure a capture graph. Most capture graphs have the following features in common:

The following diagram shows the types of filters in a typical capture graph. Dashed lines indicate where additional filters might be required, such as decoder filters or splitter filters.

Capture graph

To simplify the process of building capture graphs, DirectShow provides a component called the capture graph builder. It exposes the ICaptureGraphBuilder2 interface, which has methods for building and controlling capture graphs. This topic assumes you will use the capture graph builder in your capture applications.

Capture Filters

A capture filter delivers data through one or more output pins. An output pin can be classified by what media type it delivers, and by its pin category.

The media type is represented by a major type GUID. The most common media types are shown in the following table.

Media typeMajor type GUID
AudioMEDIATYPE_Audio
Interleaved digital video (DV)MEDIATYPE_Interleaved
MPEG StreamMEDIATYPE_Stream
VideoMEDIATYPE_Video

The pin category describes the purpose or function of the pin, and is represented by a property set GUID. There are several pin categories; this topic discusses two of them:

For a list of all the pin categories, see Pin Property Set.

You can query a pin directly for its media type and pin category. To retrieve the media type, call the IPin::EnumMediaTypes method. To retrieve the pin category, call the IKsPropertySet::Get method. Using ICaptureGraphBuilder2 methods, however, you can limit operations to a specific media type or pin category, without querying each pin.

Building a Capture Graph

To build a capture graph, perform the following steps:

  1. Create the required components.
  2. Select a capture device.
  3. Build the file-writing section.
  4. Render the streams.

Creating the Required Components

The first step in building a capture graph is to create an empty filter graph and a capture graph builder. Then call ICaptureGraphBuilder2::SetFiltergraph to associate the filter graph with the capture graph builder. The following code example shows these steps:

IGraphBuilder *pGraph = NULL;
ICaptureGraphBuilder2 *pBuilder = NULL;

// Create the filter graph.
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
    IID_IGraphBuilder, (void **)&pGraph);

// Create the capture graph builder.
CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, 
    IID_ICaptureGraphBuilder2, (void **)&pBuilder);

pBuilder->SetFiltergraph(pGraph);    

Selecting a Capture Device

The second step is to select a capture device, by using the system device enumerator. For more information, see Using the System Device Enumerator. The following code example enumerates the video capture devices and chooses the first device in the enumeration sequence:

 
// Create the system device enumerator.
ICreateDevEnum *pDevEnum = NULL;
CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, 
    IID_ICreateDevEnum, (void **)&pDevEnum);

// Create an enumerator for video capture devices.
IEnumMoniker *pClassEnum = NULL;
pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);

ULONG cFetched;
IMoniker *pMoniker = NULL;
IBaseFilter *psrc = null;
if (pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK)
{
    // Bind the first moniker to a filter object.
    pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pSrc);
    pMoniker->Release();
}
pClassEnum->Release();
pDevEnum->Release();

It would be more typical to have the user choose a device—for example, by displaying a list box with the names of available devices. To do this, call the IMoniker::BindToStorage method to obtain a property bag for the device moniker, and call the IPropertyBag::Read method to retrieve the device's friendly name. For more information, see Using the System Device Enumerator.

Now add the capture filter to the graph:

// pSrc is the capture filter from the previous code example.
pGraph->AddFilter(pSrc, L"Video Capture");

Note  A device that captures both audio and video appears in two categories: Audio Capture Sources and Video Capture Sources. After you select a device from one category, check the media types on the filter's output pins. If the device falls into both categories, don't add a second device. This also applies to digital video (DV) cameras that produce interleaved audio/video.

Building the File-Writing Section

To perform file capture, add a file-writing section to the filter graph. (If you just want preview functionality, skip this step.) Use the ICaptureGraphBuilder2::SetOutputFileName method. This method adds the required mux and file-writing filters to the graph. It takes input parameters that specify the name and format of the output file. For the format, you can use any of the following values:

The third option is for using third-party filters. For example, some capture devices deliver MPEG data. DirectShow does not include an MPEG writer filter, so you would need a third-party filter to write an MPEG file.

The following example sets the output file name to Example.avi and sets the format to AVI:

IBaseFilter     *ppf = NULL;
IFileSinkFilter *pSink = NULL;
pBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, L"C:\\Example.avi", &ppf, &pSink);

When the method returns, it sets the ppf parameter to the multiplexer's IBaseFilter interface, and it sets the pSink parameter to the file writer's IFileSinkFilter interface. These might be the same filter. For example, the ASF Writer filter does both file writing and multiplexing.

The following diagram shows the filters used to write an AVI file (left) and an ASF file (right).

Filters used to write AVI and ASF files

The AVI Mux filter exposes the IConfigAviMux and IConfigInterleaving interfaces, and the ASF Writer filter exposes the IConfigAsfWriter interface. You can use these interfaces to configure the output file format. For ASF files, you must also provide a software certificate. For more information, see Creating ASF Files in DirectShow.

Note  It is recommended that you preallocate your capture file using the ICaptureGraphBuilder2::AllocCapFile method. Capturing to a large preallocated file can improve capture performance.

Rendering the Streams

The final step in building a capture graph is to render the streams in the graph. For file capture, the capture pin connects to the file-writing section described in the preceding section. For preview, the preview pin connects to audio and video renderers. However, if the capture filter lacks a preview pin, you must use the capture pin instead. In order to use the same pin for capture and preview, the capture pin must be connected to the Smart Tee filter. This filter splits the data into two streams, a capture stream and a preview stream.

The following diagram shows a graph that uses the Smart Tee filter.

Smart Tee

The ICaptureGraphBuilder2::RenderStream method handles all of these tasks, including adding the Smart Tee filter if necessary. In addition, this method automatically adds any supporting filters that are needed for WDM devices. The following code example renders a video stream for file capture:

pBuilder->RenderStream(
        &PIN_CATEGORY_CAPTURE,  // Pin category
        &MEDIATYPE_Video,       // Media type
        pSrc,                   // Capture filter
        NULL,                   // Compression filter (optional)
        ppf                     // Multiplexer or renderer filter
    );

The first two parameters specify the pin category and media type. The next three parameters are pointers to the following filters:

To render a stream for preview, use PIN_CATEGORY_PREVIEW for the pin category and NULL for the last two parameters. If the capture filter does not have a preview pin, the capture graph builder automatically uses the Smart Tee filter. The following code example renders the video preview stream:

pBuilder->RenderStream(
        &PIN_CATEGORY_PREVIEW, 
        &MEDIATYPE_Video, 
        pSrc, 
        NULL,   // No compression filter.
        NULL    // Default renderer.
    );

Repeat these calls to RenderStream for each media type that you want to render.

Note  Some DV capture filters have one pin for interleaved data (MEDIATYPE_Interleaved) and another for video-only data (MEDIATYPE_Video). Render one of them, not both. If you render the video-only pin, your capture data will not have audio.

Setting Graph Properties

Various filters and pins in the graph might expose interfaces for setting and retrieving properties. Use the ICaptureGraphBuilder2::FindInterface method to obtain pointers to these interfaces.

Depending on your application, the interfaces shown in the following table might be useful.

IAMAudioInputMixerSets audio input properties.
IAMDroppedFramesRetrieves dropped-frame information.
IAMVfwCaptureDialogsDisplays dialog boxes provided for Microsoft® Video for Windows® capture drivers.
IAMVideoCompressionSets compression parameters.

The following code example retrieves the IAMDroppedFrames interface from an interleaved audio/video pin:

IAMDroppedFrames *pDropped = NULL;
pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, 
    pSrc, IID_IAMDroppedFrames, (void **)&pDropped);

However, do not call the FindInterface method to obtain the IVideoWindow interface, which contains methods for controlling the video playback window. Instead, query the filter graph for this interface. Otherwise, the filter graph will not respond correctly to events such as a change in the screen resolution. You can use IVideoWindow to make the preview window a child of your application window. For more information, see Setting the Video Window.

Note  The capture graph builder might add a preview window even when you don't render a preview stream. Some hardware devices use video port extensions (VPE) for video capture, and these devices require a preview window. Therefore, it's always a good idea to query the filter graph for IVideoWindow. If it exposes the interface, make the video window a child of your application window.

Controlling a Capture Graph

The filter graph's IMediaControl interface has methods for running, stopping, and pausing the entire graph. However, if the graph has multiple streams, you might want independent control over them. For example, you might want the preview stream to run while the capture stream is stopped. Or, you might want to mute the audio during preview but enable it during file capture.

The ICaptureGraphBuilder2::ControlStream method provides a way to set the start and stop times on individual streams. Call this method before you call IMediaControl::Run. When you call Run, any streams not included in your call to ControlStream start immediately.

The following code example starts the video capture stream at 2 seconds and stops it at 5 seconds:

IMediaControl   *pControl;
REFERENCE_TIME  rtStart = 20000000, 
                rtStop = 50000000;

pBuilder->ControlStream(
        &PIN_CATEGORY_CAPTURE, 
        &MEDIATYPE_Video, 
        pSrc,       // Source filter
        &rtStart,   // Start time
        &rtStop,    // Stop time
        0,          // Start cookie
        0           // Stop cookie
    );
pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
pControl->Run();

The first three parameters specify the stream to control, by pin category, media type, and capture filter. The last two parameters are cookies—arbitrary values defined in your application—which you can use to identify the stop and start events. For more information, see ICaptureGraphBuilder2::ControlStream.

To control several streams at once, omit the media type and source filter. The following example controls all capture streams in the graph:

pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, NULL, 
    &rtStart, &rtStop, 0, 0);
pControl->Run();

Device Removal

If the user removes a Plug and Play device that the graph was using, the filter graph manager posts an EC_DEVICE_LOST event. If the device becomes available again, the filter graph manager posts another EC_DEVICE_LOST event. However, the previous state of the capture filter is no longer valid. The application must rebuild the graph to use the device. For more information, see Event Notification in DirectShow.