## 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 Video for Windows-style drivers.

Note This article relies heavily on the AMCap Sample (DirectShow Capture Application) sample application. See the AMCap sample code (Amcap.cpp) in the Samples\DS\Capture directory of the DirectShow SDK for complete sample code, because this article does not present AMCap Sample (DirectShow Capture Application) in its entirety.

The AMCap Sample (DirectShow Capture Application) sample application performs video and audio capture, similar to the VidCap sample from Video for Windows®. It uses the ICaptureGraphBuilder 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 ICaptureGraphBuilder 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. See Filter Graph Manager and Filter Graphs and About Capture Filter Graphs for more information.

Contents of this article:

- Introduction to ICaptureGraphBuilder
- Device Enumeration and Capture Interfaces
- Building the Capture and Preview Filter Graph
- Controlling the Capture Filter Graph
- Obtaining Capture Statistics
- Saving the Captured File
- Displaying Property Pages

Introduction to ICaptureGraphBuilder

The ICaptureGraphBuilder 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 ICaptureGraphBuilder. The variety of methods satisfies the basic requirements for capture and preview functionality.

The FindInterface method searches for a particular capture-related interface in the filter graph. The method handles the complexities of filter graph traversal for you, which enables you to access the functionality of a particular interface without having to enumerate pins and filters in the filter graph looking for the interface. The RenderStream method connects source filters to rendering filters, optionally adding other needed intermediate filters. The ControlStream method independently control sections of the graph for frame-accurate start and stop.

Additional methods deal with allocating space for the capture file (AllocCapFile), specifying a name for it and building up the file writer section of the graph, which consists of the multiplexer and file writer filters (SetOutputFileName), and saving the captured data to another file (CopyCaptureFile). Finally, SetFiltergraph and GetFiltergraph enable the application to provide a filter graph for the graph builder to use or retrieve the filter graph already in use.

Device Enumeration and Capture Interfaces

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

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

For your convenience, the declaration of the gcap structure follows:

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

AMCap's InitCapFilters function stores several interface pointers in the gcap structure. Be sure to properly release all interface pointers 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;

See Enumerate and Access Hardware Devices in DirectShow Applications for more information about device enumeration.

Building the Capture and Preview Filter Graph

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

- Set the Capture File Name
- Create a Graph Builder Object
- Set the Output File Name
- Retrieve the Current Filter Graph
- Add the Capture Filters to the Filter Graph
- Render the Capture Pins
- Render the Video Preview Pin
- Configure the Video Preview Window

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

AMCap also includes a BuildPreviewGraph function that is essentially a version of BuildCaptureGraph that deals only with preview. Another difference between BuildCaptureGraph and BuildPreviewGraph is that the latter uses ICaptureGraphBuilder::SetFiltergraph to provide a filter graph object (IGraphBuilder interface) for the capture graph builder object (ICaptureGraphBuilder interface) to use. You probably won't need to call SetFiltergraph as 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, this 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 to enable the user to select a capture file. If the specified file is a new file, it calls the application-defined AllocCaptureFile function that 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 (it has been preallocated). The ICaptureGraphBuilder::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 ICaptureGraphBuilder::SetOutputFileName to add the file writer to the filter graph. See Set the Output File Name for more information.

The AMCap-defined SetCaptureFile and AllocCaptureFile functions follow:

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

          // We have a capture file name.

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

        SetAppCaption();      // Need a new app caption.

        // 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)
    {
    // We'll get into an infinite loop in the dlg proc setting a value.
        if (gcap.szCaptureFile[0] == 0)
          return FALSE;

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

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

          // User has hit OK. Alloc 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 ICaptureGraphBuilder 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 we can use for capture graph building.
    //
    BOOL MakeBuilder()
    {
        // We have one already.
        if (gcap.pBuilder)
          return TRUE;

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

Set the Output File Name

AMCap creates the rendering section of the filter graph, consisting of the AVI MUX (multiplexer) and the File Writer. It also provides the filter graph with the previously specified file name to which to save the captured data. See About Capture Filter Graphs for more information about capture filter graph in general.

ICaptureGraphBuilder::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.

    //
    // We need 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 above code fragment the value of the first parameter, pType, in the call to SetOutputFileName 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 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 for you by the capture graph builder object upon return from SetOutputFileName. 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 which 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 filters besides the multiplexer and file writer in the rendering section of your filter graph, call IFilterGraph::AddFilter to explicitly add the necessary filters. You might need to remember the pointer to the IBaseFilter interface of the first filter in your custom rendering chain so you can use it in 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 ICaptureGraphBuilder::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.

    //
    // The graph builder created a filter graph to do that.  Find out what it is,
    // and put the video capture filter in the graph too.
    //

        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 ICaptureGraphBuilder::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).

    //
    // Render the video capture and preview pins - we may not have preview, so
    // don't worry if it doesn't work.
    //

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

Call ICaptureGraphBuilder::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, gcap.pACap, NULL, gcap.pRender);
        // Error checking.

Render the Video Preview Pin

Call ICaptureGraphBuilder::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, gcap.pVCap,
                                         NULL, NULL);

Configure the Video Preview Window

By default, the video preview window will be a separate window from your application window. If you want to change the default behavior, call ICaptureGraphBuilder::FindInterface to obtain a pointer to the IVideoWindow interface. The first parameter, pCategory specifies the output pin category to search for a connected filter that supports the desired interface. The code fragment below uses PIN_CATEGORY_PREVIEW to indicate a search beginning with all preview pins, and continuing to any pins and filters that connect to the preview pins. The second parameter, specified by the gcap.pVCap variable below, represents the video capture filter. The third (riid) is the identifier for the desired interface (IID_IVideoWindow), and the last will be filled upon return from this function to give you the IVideoWindow interface. After you have the IVideoWindow interface, 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 to position it as desired.

    // This will go through a possible decoder, find the video renderer it's
    // connected to, and get the IVideoWindow interface on it.
       hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, gcap.pVCap,
                                         IID_IVideoWindow, (void **)&gcap.pVW);
        if (hr != NOERROR) {
          ErrMsg("This graph cannot preview");
        } else {
          RECT rc;
          gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now.
          gcap.pVW->put_WindowStyle(WS_CHILD);    // You are now a 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;
          gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // Be this big.
          gcap.pVW->put_Visible(OATRUE);
        }

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

Controlling the Capture Filter Graph

Because a capture filter graph constructed by the ICaptureGraphBuilder 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, ICaptureGraphBuilder 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 ICaptureGraphBuilder::ControlStream method's first parameter (pCategory) is a pointer to a GUID that specifies the output pin category. This value is normally either PIN_CATEGORY_CAPTURE or PIN_CATEGORY_PREVIEW. See the Pin Property Set for a complete list of categories. Specify NULL to control all capture filters in the graph.

The second parameter (pFilter) in ICaptureGraphBuilder::ControlStream indicates which filter to control. Specify NULL to control the whole filter graph as AMCap does.

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

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 ICaptureGraphBuilder::ControlStream, so it doesn't need any cookies. If you use a cookie, use IMediaEvent to get event notifications. See IAMStreamControl for more information.

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, &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 (third) parameter is NULL). Specifying MAX_TIME for the stop time (pstop) means disregard the stop time.

        gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW, 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 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 ICaptureGraphBuilder::ControlStream. For example, the following call controls the whole filter graph (NULL pFilter (second) parameter), starts now (NULL pstart (third) parameter), and never stops (pstop (fourth) parameter initialized to MAX_TIME).

        REFERENCE_TIME stop = MAX_TIME;

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

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

To stop the capture or preview operation, call IMediaControl::Stop, much as you called 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 Win32 timeGetTime function at the beginning and end of capture to determine the capture operation's duration. The IAMDroppedFrames::GetAverageFrameSize method provides the average size of captured frames in bytes. Use the information from IAMDroppedFrames::GetNumNotDropped, timeGetTime, and IAMDroppedFrames::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 so you can capture as quickly as possible. When you want to save the data you captured to a more permanent location, call ICaptureGraphBuilder::CopyCaptureFile. This method transfers the captured data out of the previously allocated capture file to another file you choose. The resulting new file size matches the size of the actual captured data rather than the preallocated file size, which is usually very large.

The ICaptureGraphBuilder::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 want to save your captured data. Setting the third parameter, fAllowEscAbort, to TRUE indicates that the user is allowed to abort the copy operation by pressing ESC. The last parameter, pCallback, is optional and enables you to supply a progress indicator, if desired, 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 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 ICaptureGraphBuilder::CopyCaptureFile.

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

          // We have 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...");

          // We need our own graph builder because the main one might not exist.
          ICaptureGraphBuilder *pBuilder;
          hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder,
                                NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder,
                                (void **)&pBuilder);
          if (hr == NOERROR) {
              // Allow 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 {
    return TRUE;    // They canceled or something.
        }
    }

See Amcap.cpp and Status.cpp from the AMCap sample for more details about capturing media files and obtaining capture statistics.

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, IAMVideoProcAmp. Creating a property page is one way of allowing 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 be later passed to OleCreatePropertyFrame or OleCreatePropertyFrameIndirect 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 9 objects that can have property pages in capture applications. Capture applications usually have 2 of these objects at least; the video capture filter and the audio capture filter (call them pVCap and pACap). These objects expose the IBaseFilter interface which can be used to query for the ISpecifyPropertyPages interface. You can obtain a pointer to the other 7 objects as follows:

1. The video capture filter's capture pin. Get this by calling :

       FindInterface(&PIN_CATEGORY_CAPTURE, pVCap, IID_IPin, &pX);

2. The video capture filter's preview pin. Get this by calling:

       FindInterface(&PIN_CATEGORY_PREVIEW, pVCap, IID_IPin, &pX);

3. The audio capture filter's capture pin. Get this by calling:

       FindInterface(&PIN_CATEGORY_CAPTURE, pACap, IID_IPin, &pX);

4. The crossbar connected to the video capture filter. Get this by calling:

       FindInterface(NULL, pVCap, IID_IAMCrossbar, &pX);

5. The crossbar connected to the audio capture filter. This might be the same as object #4. Compare their IUnknown interfaces to find out. Get this by calling:

       FindInterface(NULL, pACap, IID_IAMCrossbar, &pX);

6. The TV Tuner connected to the video capture filter. Get this by calling:

       FindInterface(NULL, pVCap, IID_IAMTVTuner, &pX);

7. The TV Audio connected to the audio capture filter. Get this by calling:

       FindInterface(NULL, pACap, IID_IAMTVAudio, &pX);

If you do not wish to create your property page 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.

© 1997 Microsoft Corporation. All rights reserved. Terms of Use.