Creating a Capture Application

Microsoft® DirectShow® provides the capability to capture and preview both video and audio data from an application, when combined with the appropriate capture hardware. The data source might include a VCR, camera, TV tuner, microphone, or other source. An application can display the captured data immediately (preview) or save it to a file for later viewing either inside or outside of the application.

DirectShow takes advantage of new capture drivers that are written as DirectShow filters, and also uses existing Microsoft Video for Windows®-style drivers.

Note This article relies heavily on the AMCap sample application. For complete sample code, see the AMCap sample code (Amcap.cpp) in the Samples\Multimedia\DShow\Src\Capture directory of the DirectX Media Software Development Kit (SDK), because this article does not present the AMCap sample application in its entirety.

The AMCap sample application performs video and audio capture, like the VidCap sample from Video for Windows. It uses the ICaptureGraphBuilder2 interface to handle the majority of the capture work. In your own capture application, you'll use the same methods and interfaces that AMCap uses. This article focuses on AMCap's use of ICaptureGraphBuilder2 to perform video and audio capture and presents relevant code excerpts from AMCap.

This article assumes you are familiar with the DirectShow filter graph architecture and the general layout of a capture filter graph. For more information, see Filter Graph Manager and Filter Graphs and About Capture Filter Graphs.

This article contains the following sections.

Introduction to ICaptureGraphBuilder2

The ICaptureGraphBuilder2 interface provides a filter graph builder object that applications use to handle some of the more tedious tasks involved in building a capture filter graph, which frees the application to focus on capture. You access the graph builder object by calling methods on ICaptureGraphBuilder2. The methods provided satisfy the basic requirements for capture and preview functionality.

The FindInterface method searches for a particular capture-related interface in the filter graph. It handles the complexities of filter graph traversal for you, enabling you to access the functionality of a particular interface without having to look for the interface by enumerating pins and filters in the filter graph. The RenderStream method connects source filters to rendering filters, with the option to add intermediate filters if needed. The ControlStream method independently controls sections of the graph for frame-accurate start and stop.

Additional methods allocate space for the capture file (AllocCapFile), specify a name for the file and build up the file writer section of the graph (SetOutputFileName), and save the captured data to another file (CopyCaptureFile). Finally, SetFiltergraph enables the application to provide a filter graph for the graph builder, and GetFiltergraph retrieves the filter graph already in use.

Only very simple capture examples will be shown here. For full information about capturing and previewing, consult the AMCap SDK sample code.

Device Enumeration and Capture Interfaces

AMCap's InitCapFilters function uses the ICreateDevEnum::CreateClassEnumerator method to enumerate the capture devices on the system. After enumerating a capture device and instantiating a DirectShow filter to use that device, the sample calls the ICaptureGraphBuilder2::FindInterface method several times to obtain interface pointers for the IAMDroppedFrames, IAMVideoCompression, IAMStreamConfig, and IAMVfwCaptureDialogs capture-related interfaces. The AMCap code saves all these interface pointers for later use in the gcap global structure and uses gcap structure members throughout the code.

Note IAMVfwCaptureDialogs is designed only for use with the Microsoft-supplied video capture filter and only when using a former Video for Windows device.

The declaration of the gcap structure follows:

struct _capstuff {
    char szCaptureFile[_MAX_PATH];
    WORD wCapFileSize;          // size in MB
    ICaptureGraphBuilder2 *pBuilder;
    IVideoWindow *pVW;
    IMediaEventEx *pME;
    IAMDroppedFrames *pDF;
    IAMVideoCompression *pVC;
    IAMVfwCaptureDialogs *pDlg;
    IAMStreamConfig *pASC;      // for audio capture
    IAMStreamConfig *pVSC;      // for video capture
    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;

The InitCapFilters function stores several interface pointers in the gcap structure. Be sure to release all interface pointers properly when they are no longer needed, as shown in the following example.

    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;

For more information about device enumeration, see Enumerate and Access Hardware Devices in DirectShow Applications.

Building the Capture and Preview Filter Graph

AMCap includes a BuildCaptureGraph function that builds a capture graph with both capture and preview components. Most applications will perform the same tasks sequentially as described in the following topics.

These tasks are explained in greater detail later in this section.

AMCap also includes a BuildPreviewGraph function, essentially a version of BuildCaptureGraph that deals only with preview. Another difference between BuildCaptureGraph and BuildPreviewGraph is that the latter uses ICaptureGraphBuilder2::SetFiltergraph to provide a filter graph object (an IGraphBuilder interface) for the capture graph builder object (an ICaptureGraphBuilder2 interface) to use. You probably won't need to call SetFiltergraph in your own application, because the graph builder object creates a filter graph to use by default. Use this method only if you have already created your own filter graph and want the graph builder to use your filter graph. If you call this method after the graph builder has created a filter graph, the method will fail. BuildPreviewGraph calls CoCreateInstance to create a new filter graph object, if necessary, as shown in the following example.

hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
                      IID_IGraphBuilder, (LPVOID *)&gcap.pFg);

hr = gcap.pBuilder->SetFiltergraph(gcap.pFg);
if (hr != NOERROR) {
    ErrMsg("Cannot give graph to builder");
    goto SetupPreviewFail;
}

The details of each important task performed by BuildCaptureGraph follow.

Set the Capture File Name

AMCap's SetCaptureFile function displays the common Open File dialog box, enabling the user to select a capture file. If the user specifies a new file, SetCaptureFile calls the application-defined AllocCaptureFile function, which prompts the user to allocate space for the capture file. This "preallocation" of file space is important, because it reserves a large block of space on disk. This speeds up the capture operation when it occurs, because the file space doesn't have to be allocated while capture takes place. The ICaptureGraphBuilder2::AllocCapFile method performs the actual file allocation. IFileSinkFilter::SetFileName instructs the file writer filter to use the file name that the user chose. The code assumes you've called ICaptureGraphBuilder2::SetOutputFileName to add the file writer to the filter graph. For more information, see Set the Output File Name.

The AMCap-defined SetCaptureFile and AllocCaptureFile functions follow:

// Put up a dialog to enable the user to select a capture file.
BOOL SetCaptureFile(HWND hWnd)
{
    if (OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH)) {
        OFSTRUCT os;

    // A capture file name exists.

        // If this is a new file, 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;
    }

    // Need a new application caption
    SetAppCaption();      

    // 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)
{
// This will create an infinite loop in the dialog processing.
    if (gcap.szCaptureFile[0] == 0)
      return FALSE;

    // Show the allocate file space dialog to encourage
    // the user to preallocate space.
    if (DoDialog(hWnd, IDD_AllocCapFileSpace, AllocCapFileProc, 0)) {

        // Ensure repaint after dismissing the dialog before
        // a possibly lengthy operation.
        UpdateWindow(ghwndApp);

        // User has hit OK. Allocate the 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;
        }
}

Create a Graph Builder Object

AMCap's MakeBuilder function creates a capture graph builder object and obtains an ICaptureGraphBuilder2 interface pointer by calling CoCreateInstance. If you already have a capture graph builder object, you can call QueryInterface to obtain an interface pointer. AMCap stores the object pointer in the pBuilder member of the global gcap structure.

// Make a graph builder object to use for capture graph building.
BOOL MakeBuilder()
{
    // Graph builder already exists.
    if (gcap.pBuilder)
        return TRUE;

    HRESULT hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2,
                    NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2,
                    (void **)&gcap.pBuilder);
    return (hr == NOERROR) ? TRUE : FALSE;
}

Set the Output File Name

AMCap creates the filter graph's rendering section, which consists of the AVI MUX (multiplexer) and the File Writer. It also provides the filter graph with the file name that was previously specified for saving the captured data. For more information about capture filter graphs, see About Capture Filter Graphs.

ICaptureGraphBuilder2::SetOutputFileName signals to add the multiplexer and file writer to the filter graph, connects them, and sets the file name. The following example illustrates a call to SetOutputFileName.

// Create 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;
    }

In the preceding call to SetOutputFileName, the value of the first parameter (pType) is MEDIASUBTYPE_Avi, indicating that the capture graph builder object will insert an AVI multiplexer filter. Consequently, the file writer that is connected to the multiplexer will write the data to the capture file in Audio-Video Interleaved (AVI) file format.

The second parameter, lpwstrFile, specifies the file name. The last two parameters contain pointers to the multiplexer filter and the file writer filter, respectively, and are initialized by the capture graph builder object after SetOutputFileName returns. AMCap stores these pointers in the pRender and pSink members of its gcap structure. Internally, the capture graph builder object creates a filter graph object that exposes the IGraphBuilder interface and inserts these two filters into that filter graph. It tells the file writer to use the specified file when writing to disk.

Alternatively, if you want to use filters other than the multiplexer and file writer in the rendering section of your filter graph, call IFilterGraph::AddFilter to explicitly add them. You might need to keep a pointer to the IBaseFilter interface of the first filter in your custom rendering chain, so that you can use it in later calls (such as RenderStream).

Retrieve the Current Filter Graph

Because the capture graph builder object created a filter graph in response to SetOutputFileName and you must put the necessary filters in the same filter graph, call the ICaptureGraphBuilder2::GetFiltergraph method to retrieve the newly created filter graph. The pointer to the filter graph's IGraphBuilder interface is returned in the function's parameter.

    hr = gcap.pBuilder->GetFiltergraph(&gcap.pFg);
    if (hr != NOERROR) {
        ErrMsg("Error %x: Cannot get filtergraph", hr);
        goto SetupCaptureFail;
    }

Add the Capture Filters to the Filter Graph

Call IFilterGraph::AddFilter to add the capture filters to the filter graph, as shown in the following example.

    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;
        }

Render the Capture Pins

The ICaptureGraphBuilder2::RenderStream method connects the source filter's pin to the rendering filter. It connects intermediate filters if necessary. The pin category is optional, but typically specifies either a capture pin (PIN_CATEGORY_CAPTURE) or a preview pin (PIN_CATEGORY_PREVIEW). The following example connects the capture pin on the video capture filter (represented by the gcap.pVCap variable) to the renderer (represented by gcap.pRender).

hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
    gcap.pVCap, NULL, gcap.pRender);

Call RenderStream again to connect the audio capture filter (represented by gcap.pACap) to the audio renderer, as in the following example.

// Render the audio capture pin?

    if (gcap.fCapAudio) {
        hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio,
	  			gcap.pACap, NULL, gcap.pRender);

Render the Video Preview Pin

Call ICaptureGraphBuilder2::RenderStream again to render the graph from the capture filter's preview pin to a video renderer, as in the following example.

    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
					gcap.pVCap, NULL, NULL);

Configure the Video Preview Window

By default, the video preview window will be a separate window from your application window. To change the default behavior, obtain a pointer to the IVideoWindow interface by using the QueryInterface method on the current IGraphBuilder interface. You should not use the ICaptureGraphBuilder2::FindInterface method to obtain the IVideoWindow pointer, because the filter graph needs to know that the application owns the window, so that it can forward messages to it.

After you have the interface pointer, you can call IVideoWindow methods such as put_Owner, put_WindowStyle, or SetWindowPosition to take ownership of the video preview window, make it a child of your application, or position it as desired. Also, you can use the NotifyOwnerMessage method to retrieve messages that the parent window has received, such as display change, palette change, and system color change.

// This will find the IVideoWindow interface on the renderer. It is 
// important to ask the filter graph for this interface. Do not use
// ICaptureGraphBuilder2::FindInterface, because the filter graph needs to
// know we own the window.

hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
if (hr != NOERROR) {
	ErrMsg("This graph cannot preview properly");
} else {
	RECT rc;
    // We own the window now.
	gcap.pVW->put_Owner((long)ghwndApp);
	// You are now a child.
    gcap.pVW->put_WindowStyle(WS_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;
	// Be this big.
    gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom);
	gcap.pVW->put_Visible(OATRUE);
}

Now that you've built the entire capture filter graph, you can preview video and audio, or actually capture data.

Controlling the Capture Filter Graph

Because a capture filter graph constructed by the ICaptureGraphBuilder2 interface is simply a specialized filter graph, controlling a capture filter graph is much like controlling any other kind of filter graph: You use the IMediaControl interface's Run, Pause, and Stop methods. You can use the CBaseFilter::Pause method to cue things up, but remember that capture and recompression only happen when the graph is running. In addition, ICaptureGraphBuilder2 provides the ControlStream method to control the start and stop times of the capture filter graph's streams. Internally, ControlStream calls the IAMStreamControl::StartAt and IAMStreamControl::StopAt methods to start and stop the capture and preview portions of the filter graph for frame-accurate control.

Note This method might not work on every capture filter because not every capture filter supports IAMStreamControl on its pins.

The ControlStream method's first parameter (pCategory) is a pointer to a GUID that specifies the output pin category. This value is usually either PIN_CATEGORY_CAPTURE or PIN_CATEGORY_PREVIEW. For a complete list of categories, see Pin Property Set. Specify NULL to control all capture filters in the graph.

The second parameter (pType) is the media type of the pin to control (video or audio). The third parameter (pFilter) indicates which filter to control. Specify NULL to control the whole filter graph as AMCap does.

The third and fourth parameters (pstart and pstop) indicate the desired start and stop times, respectively. For both of these parameters, the value NULL means immediately, and the value MAX_TIME means to ignore or cancel the specified operation. (MAX_TIME is defined in the DirectShow base classes as the maximum reference time.) To run only the preview portion of the capture filter graph, prevent capture by calling ICaptureGraphBuilder2::ControlStream with the capture pin category (pCategory) and the value MAX_TIME as the start time (pstart). Call the method again with preview as the pin category, a NULL start value to start preview immediately, and a stop time of MAX_TIME.

The last two parameters, wStartCookie and wStopCookie are start and stop cookies, respectively. These cookies are arbitrary values set by the application so that it can differentiate between start and stop times and tell when specific actions have been completed. AMCap doesn't use a specific time in ICaptureGraphBuilder2::ControlStream, so it doesn't need any cookies. If you use a cookie, use IMediaEvent to get event notifications. For more information, see IAMStreamControl.

The following code fragment sets preview to start immediately, but ignores capture.

    // 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, NULL,
				&start, NULL, 0, 0);

The same concept applies if you want only to capture and not preview. Set the capture start time to NULL to capture immediately and set the capture stop time to MAX_TIME. Set the preview start time to MAX_TIME, with an immediate (NULL) stop time.

The following example tells the filter graph to start the preview stream now (the pstart (fourth) parameter is NULL). Specifying MAX_TIME for the stop time (pstop) means disregard the stop time.

    gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW, NULL, NULL, NULL,
			MAX_TIME, 0, 0); 

Calling IMediaControl::Run runs the 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 the parts that ran.
            pMC->Stop();
        }
        pMC->Release();
    }
    if (FAILED(hr)) {
        ErrMsg("Error %x: Cannot run preview graph", hr);
        return FALSE;
	}

If the graph is already running, start capture immediately with another call to ICaptureGraphBuilder2::ControlStream. For example, the following call controls the whole filter graph (third parameter, pFilter, is NULL), starts now (fourth parameter, pstart, is NULL), and never stops (fifth parameter, pstop, is MAX_TIME).

    REFERENCE_TIME stop = MAX_TIME;

    gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, NULL, NULL,
				&stop, 0, 0);

AMCap uses this approach to start capture in response to the user clicking a mouse button.

To stop the capture or preview operation, call IMediaControl::Stop, much as you call IMediaControl::Run to run the filter graph.

// Stop the graph.
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr)) {
        hr = pMC->Stop();
        pMC->Release();
    }

Obtaining Capture Statistics

AMCap calls methods on the IAMDroppedFrames interface to obtain capture statistics. It determines the number of frames dropped (IAMDroppedFrames::GetNumDropped) and captured (IAMDroppedFrames::GetNumNotDropped), and uses the Microsoft® Win32® timeGetTime function at the beginning and end of capture to determine the duration of the capture operation. The IAMDroppedFrames::GetAverageFrameSize method provides the average size of captured frames in bytes. Use the information from GetNumNotDropped, timeGetTime, and GetAverageFrameSize to obtain the total bytes captured and calculate the sustained frames per second for the capture operation.

Saving the Captured File

The original preallocated capture file temporarily holds capture data in order to capture as quickly as possible. When you want to save the data you captured to a more permanent location, call ICaptureGraphBuilder2::CopyCaptureFile. This method transfers the captured data out of the previously allocated capture file to another file you choose. The size of the resulting new file matches that of the actual captured data rather than the preallocated file size, which is usually very large.

The CopyCaptureFile method's first parameter, lpwstrOld, is the file you're copying from (typically the very large, preallocated file you always use for capture). The second parameter, lpwstrNew, is the file to which you will save your captured data. Setting the third parameter, fAllowEscAbort, to TRUE indicates that the user can abort the copy operation by pressing ESC. The last parameter, pCallback, is optional and enables you to supply a progress indicator by implementing the IAMCopyCaptureFileProgress interface. The following example demonstrates a call to CopyCaptureFile.

    hr = pBuilder->CopyCaptureFile(wachSrcFile, wachDstFile, TRUE, NULL);

The SaveCaptureFile function defined by AMCap prompts the user to enter a new file name in the Open File common dialog box, uses the Win32 MultiByteToWideChar function to convert the file name to a wide string, and saves the captured data to the specified file using ICaptureGraphBuilder2::CopyCaptureFile.

// Put up a dialog to enable 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)) {

        // User provided 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...");

        // A graph builder is required, because the main one might not exist.
        ICaptureGraphBuilder *pBuilder;
        hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2,
                            NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2,
                            (void **)&pBuilder);
        if (hr == NOERROR) {
            // Enable 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 {
        // They canceled or something.
        return TRUE;    
    }
}

For more details about capturing media files and obtaining capture statistics, see Amcap.cpp and Status.cpp from the AMCap sample.

Displaying Property Pages

DirectShow provides a number of interfaces to customize the settings of a capture filter graph, including: IAMStreamConfig, IAMVideoCompression, IAMCrossbar, IAMTVTuner, IAMTVAudio, IAMAnalogVideoDecoder, IAMCameraControl, and IAMVideoProcAmp. Creating a property page is one way to enable users to interact with these settings.

To bring up the settings associated with an object on a property page, use an interface on the object to query for the ISpecifyPropertyPages interface. Use this interface to obtain a list of property page CLSIDs that this object supports. The CLSID list can later be passed to the OleCreatePropertyFrame or OleCreatePropertyFrameIndirect functions to invoke a property sheet. This will supply your application with the custom property pages a filter has in addition to the standard pages.

There are at least nine objects that can have property pages in capture applications. Capture applications usually have at least two of these objects: the video capture filter and the audio capture filter. These objects expose the IBaseFilter interface, which can be used to query for the ISpecifyPropertyPages interface. You can obtain a pointer to the other seven objects as follows (pVCap and pACap are pointers to the video capture filter and audio capture filter, respectively):

  1. The video capture filter's capture pin. Get this by calling:
    FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pVCap, IID_IPin, &pX);
  2. The video capture filter's preview pin. Get this by calling:
    FindInterface(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pVCap, IID_IPin, &pX);
  3. The audio capture filter's capture pin. Get this by calling:
    FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, pACap, IID_IPin, &pX);
  4. The crossbar connected to the video capture filter. Get this by calling:
    FindInterface(NULL, NULL, pVCap, IID_IAMCrossbar, &pX);
  5. A possible second crossbar, that controls both audio and video. This crossbar, if it exists, will be upstream of the crossbar found in the preceding step. Get this by calling:
    pX->QueryInterface(IID_IBaseFilter, (void **)&pBaseFilter);
    FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pBaseFilter, IID_IAMCrossbar, &pX2);
    pBaseFilter->Release();
    
  6. The TV Tuner filter connected to the video capture filter. Get this by calling:
    FindInterface(NULL, NULL, pVCap, IID_IAMTVTuner, &pX);
  7. The TV Audio filter connected to the audio capture filter. Get this by calling:
    FindInterface(NULL, NULL, pACap, IID_IAMTVAudio, &pX);

If you do not want to create your property page by using the ISpecifyPropertyPages interface and the OleCreatePropertyFrame function, you can create your own custom property pages and use the results of your page to call the interfaces programmatically.

Dealing With Crossbars

Some Windows Driver Model (WDM) capture devices have separate filters that act as crossbars, routing the inputs and outputs of the device. There can be more than one crossbar, complicating the topology with multiple filters. Thus, the application might need to communicate with more than one filter to select an input from which to record. A helper class, CCrossbar (code is available in the AMCap SDK sample), assists an application in working with crossbars. It provides a simple way for your application to enumerate all the possible inputs (for example, two SVideo inputs and three Composite video inputs), and select a single input for recording, viewing, and so on. These helper functions streamline the process of dealing with crossbars by minimizing code and eliminating the need to configure and select recording input through a series of dialog boxes.

For information on the CCrossbar helper functions, see the CCrossbar documentation.

For an example of using the CCrossbar helper functions, see Crossbar.cpp, Crossbar.h, and Amcap.cpp of the AMCap sample.

Additional Notes

This section provides some additional notes on using the ICaptureGraphBuilder2::RenderStream method. It details specific examples of how to customize a capture graph from your application.


Top of Page Top of Page
© 2000 Microsoft and/or its suppliers. All rights reserved. Terms of Use.