Microsoft DirectX 8.0

Recompressing an AVI File

This article describes how to recompress an Audio-Video Interleaved (AVI) file using the capture graph builder, a COM object that constructs capture graphs and other custom filter graphs.

Note  Two versions of the capture graph builder ship with Microsoft® DirectShow®. They have different class identifiers and expose different interfaces. The earlier version has the class identifier CLSID_CaptureGraphBuilder and exposes the ICaptureGraphBuilder interface. It is supported for compatibility with existing applications. The new version has the class identifier CLSID_CaptureGraphBuilder2 and exposes the newer ICaptureGraphBuilder2 interface, which is more flexible than the earlier version.

For information on using ICaptureGraphBuilder2 in a video capture application, see How to Write a Capture Application. In this article, ICaptureGraphBuilder2 is used to copy media data from a source file to an output file, recompressing it along the way.

This article contains the following sections:

Creating the Graph

Start by creating an instance of the capture graph builder, as shown in the following example:

ICaptureGraphBuilder2   *pBuild = NULL;
CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, 
        IID_ICaptureGraphBuilder2, (void **)&pBuild);

The capture graph builder automatically creates a filter graph manager. Initially, the filter graph is empty, with no filters. Building the filter graph is a two-stage process:

  1. Build the rendering section of the graph, which includes the AVI MUX filter and the File Writer. The MUX filter combines video and audio streams into a single AVI stream for the file writer.
  2. Add the file source filter to the graph and connect it to the MUX filter. The capture graph builder inserts any splitter filters and decoder filters needed to parse the file format. The audio and video streams can also be routed through audio and video compression filters, if desired.

Building the Rendering Section

To build the rendering section of the graph, call the ICaptureGraphBuilder2::SetOutputFileName method. This method takes input parameters that specify the media subtype for the output, and the name of the output file. It returns with output parameters that contain pointers to the MUX filter and the file writer. The pointer to the MUX filter is needed during the next stage of building the graph. The pointer to the file writer is not needed for this example, so that parameter can be NULL. To keep the example short, the following code example uses a literal string for the file name:

IBaseFilter *pMux = NULL;
pBuild->SetOutputFileName(
        &MEDIASUBTYPE_Avi,  // File type. 
        L"Output.avi",      // File name.
        &pMux,              // Receives a pointer to the multiplexer.
        NULL);              // Receives a pointer to the file writer. 

After the method returns, the pointer to the MUX filter has an outstanding reference count, so the application must release the pointer when it is done. The MUX filter exposes interfaces for controlling the AVI format:

The following diagram shows the filter graph at this point.

Rendering Section of the Filter Graph

Connecting the Source Filter

The next stage is to connect a source filter to the rest of the graph. First, call the ICaptureGraphBuilder2::GetFiltergraph method to retrieve a pointer to the filter graph's IGraphBuilder interface. Then call the IGraphBuilder::AddSourceFilter method, which loads a file and retrieves a pointer to the file source filter.

IGraphBuilder   *pGraph = NULL;
IBaseFilter     *psrc = null;

pBuild->GetFiltergraph(&pGraph);
pGraph->AddSourceFilter(L"C:\\Input.avi", L"Source Filter", &pSrc);

Next, create instances of the video and audio compression filters that you will use to recompress the file. One approach is to enumerate the compression devices on the user's system and allow the user to select one. For information on how to do this, see Enumerating Devices and Filters. As an alternative, if you have a compression filter that you want to use, you can directly create an instance of it, through a call to CoCreateInstance. After creating an instance of a compression filter, add it to the filter graph by calling IFilterGraph::AddFilter.

The following code example create an instance of the AVI Compressor filter, which compresses video. In this example, the audio is left uncompressed.

CoCreateInstance(CLSID_AVICo, NULL, CLSCTX_INPROC,  
        IID_IBaseFilter, (void **)&pVComp); 
pGraph->AddFilter(pVComp, L"Compressor");

At this point, the source filter and the compression filter are not connected to anything else in the graph, as shown in the following illustration.

Filter Graph with Source and Compression Filters

The final step in building the graph is to render the video and audio streams, using the ICaptureGraphBuilder2::RenderStream method. This method connects an output pin on the source filter to the rendering section of the graph, optionally through a compression filter.

The methods in ICaptureGraphBuilder2 are designed to work with live capture sources, such as video cameras, which can have separate output pins for preview and capture. In that situation, the first two parameters of RenderStream specify which pin to connect, by designating a pin category and a media type. With a file source filter, there is only one output pin, so it is not necessary to specify these parameters.

The next three parameters are pointers to the source filter (or a pin on the source filter), the compression filter, and the sink filter. For writing to a file, the last of these should be a pointer to the multiplexer.

The following code example renders the video stream through the video compressor:

pBuild->RenderStream(
        NULL,       // Output pin category
        NULL,       // Media type
        pSrc,       // Source filter
        pVComp,     // Compressor filter
        pMux);      // Sink filter (the AVI Mux)

The following diagram shows the filter graph so far.

Rendered Video Stream

The diagram assumes that the source file has two streams, one audio and one video. If so, the AVI Splitter filter has an output pin for the audio stream, but the pin is not yet connected to anything in the graph. To render the audio stream, call RenderStream again.

pBuild->RenderStream(NULL, NULL, pSrc, NULL, pMux);

The source filter's only output pin is already connected, so the RenderStream method searches for an unconnected output pin on the splitter filter. It finds the audio output pin and connects it directly to the MUX filter. If the source file has a video stream but no audio stream, the second call to RenderStream fails. This is expected behavior. The graph is complete after the first call to RenderStream, so the failure in the second call is harmless; the graph will still run.

The following diagram shows the completed filter graph.

Rendered Audio and Video Streams

The order of the two calls is important in this example, because the video stream is recompressed but the audio stream is not. The first call includes a video compressor, and therefore it specifies the video stream. The second call does not include a compressor, so it could apply to any stream. The audio stream is the only one that remains, so the call works.

However, suppose the order were switched:

pBuild->RenderStream(NULL, NULL, pSrc, NULL, pMux);
// WRONG! This might render the video stream...

pBuild->RenderStream(NULL, NULL, pSrc, pVComp, pMux);
// ... causing this to fail.

Here, the second call specifies the compressor. It is indeterminate whether the first call will render the video stream or the audio stream. If it renders the video stream, the second call fails.

On the other hand, if the example used both a video compressor and an audio compressor, the order would not matter. Each call would fully specify one stream or the other. For similar reasons, the order would not matter if the example used no compression filters. (In that case, the two calls would be identical.)

If the source file has more than two streams, the resulting filter graph is incorrect and will exhibit unexpected behaviors. This situation is uncommon, but to be safe you can enumerate the output pins on the splitter filter after you render the first stream. For more information, see Enumerating Objects in a Filter Graph.

Writing the File

Before writing the file, you can set properties on the compression filter. The example code shows how to set the bit rate and the key frame rate. For details, see the example code in the following section.

To write the file, simply run the filter graph. Call the IMediaControl::Run method, wait for playback to complete, and then explicitly stop the graph by calling IMediaControl::Stop. Otherwise, the file is not written correctly. For more information, see Responding to Events.

To display the progress of the file-writing operation, query the MUX filter for the IMediaSeeking interface. Call the IMediaSeeking::GetDuration method to retrieve the duration of the file. Periodically while the graph is running, call the IMediaSeeking::GetCurrentPosition method to retrieve the graph's current position in the stream. Current position divided by duration gives the percentage complete.

Note:  Usually, an application should query the filter graph manager for IMediaSeeking, and not query an individual filter. File writing is an exception to this rule. The filter graph manager does not retrieve the current position from the filters. Instead, it calculates the position based on the starting postion and how long the graph has been running. For file writing, this can give the wrong result, because writing the file can take longer than the file duration. Retrieving the position from the MUX filter produces an accurate result.

Sample Code

For brevity, the following code does not perform any error checking:

#include <dshow.h>
#include <stdio.h>

void __cdecl main(void)
{
    ICaptureGraphBuilder2   *pBuild = NULL;
    IGraphBuilder           *pGraph = NULL;
    IBaseFilter             *psrc = null;       // Source filter
    IBaseFilter             *pMux = NULL;       // MUX filter
    IBaseFilter             *pVComp = NULL;     // Video compressor filter

    CoInitialize(NULL);

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

    // Make the rendering section of the graph.
    pBuild->SetOutputFileName(
            &MEDIASUBTYPE_Avi,  // File type. 
            L"C:\\Output.avi",  // File name.
            &pMux,              // Receives a pointer to the multiplexer.
            NULL);              // Receives a pointer to the file writer. 
    
    // Load the source file.
    pBuild->GetFiltergraph(&pGraph);
    pGraph->AddSourceFilter(L"C:\\Input.avi", L"Source Filter", &pSrc);

    // Add the compressor filter.
    CoCreateInstance(CLSID_AVICo, NULL, CLSCTX_INPROC,  
            IID_IBaseFilter, (void **)&pVComp); 
    pGraph->AddFilter(pVComp, L"Compressor");

    // Render the video stream, through the compressor.
    pBuild->RenderStream(
            NULL,       // Output pin category
            NULL,       // Media type
            pSrc,       // Source filter
            pVComp,     // Compressor filter
            pMux);      // Sink filter (the AVI Mux)

    // Render the audio stream.
    pBuild->RenderStream(NULL, NULL, pSrc, NULL, pMux);

    // Set video compression properties.
    IAMStreamConfig         *pStreamConfig = NULL;
    IAMVideoCompression     *pCompress = NULL;

    // Compress at 100k/second data rate.
    AM_MEDIA_TYPE *pmt;
    pBuild->FindInterface(NULL, NULL, pVComp, IID_IAMStreamConfig, (void **)&pStreamConfig);                           
    pStreamConfig->GetFormat(&pmt);
    if (pmt->formattype == FORMAT_VideoInfo) 
    {
        ((VIDEOINFOHEADER *)(pmt->pbFormat))->dwBitRate = 100000;
        pStreamConfig->SetFormat(pmt);
    }
    DeleteMediaType(pmt);

    // Request key frames every four frames.
    pStreamConfig->QueryInterface(IID_IAMVideoCompression, (void **)&pCompress);
    pCompress->put_KeyFrameRate(4);
    pCompress->Release();
    pStreamConfig->Release();

    // Run the graph.
    IMediaControl   *pControl = NULL;
    IMediaSeeking   *pSeek = NULL;
    IMediaEvent     *pEvent = NULL;
    pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
    pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
   
    HRESULT hr = pMux->QueryInterface(IID_IMediaSeeking, (void**)&pSeek);

    pControl->Run();
    printf("Recompressing... \n");

    long evCode;
    if (SUCCEEDED(hr))  // IMediaSeeking is supported
    {
        REFERENCE_TIME rtTotal, rtNow = 0;
        pSeek->GetDuration(&rtTotal);
        while ((pEvent->WaitForCompletion(1000, &evCode)) == E_ABORT)
        {
            pSeek->GetCurrentPosition(&rtNow);
            printf("%d%%\n", (rtNow * 100)/rtTotal);
        }
        pSeek->Release();
    }
    else  // Cannot update the progress.
    {
        pEvent->WaitForCompletion(INFINITE, &evCode);
    }
    pControl->Stop();
    printf("All done\n");   

    pSrc->Release();
    pMux->Release();
    pVComp->Release();
    pControl->Release();
    pEvent->Release();
    pBuild->Release();
    pGraph->Release();
    CoUninitialize();
}