Microsoft DirectX 8.1 (C++)

General Graph Building

The following are general guidelines for graph building:

To create a filter graph, you always begin by creating an instance of the Filter Graph Manager and obtaining a pointer to its IGraphBuilder interface, as illustrated here:

IGraphBuilder* pIGB;
HRESULT hr;
hr = CoCreateInstance(CLSID_FilterGraph,
                    NULL,
                    CLSCTX_INPROC_SERVER,
                    IID_IGraphBuilder,
                    (void **)&pIGB);

The IGraphBuilder interface, as its name suggests, contains the methods for building filter graphs. These methods provide three basic approaches to building the graph:

  1. The application instructs the Filter Graph Manager to build the entire graph automatically.
  2. The application and Filter Graph Manager share the graph building work.
  3. The application builds the entire graph itself by individually adding and connecting each filter.

Approach #1: The Filter Graph Manager builds the entire graph

Approach #1 is for scenarios when you simply want to play a file of some recognized format, such as AVI, MPEG, WAV, MP3, and so on. This technique is demonstrated in the article How To Play a File. In DirectShow, the term render is often used instead of play to describe the process of displaying video data on the monitor or audio data through the system's speakers. To instruct the Filter Graph Manager to automatically create a filter graph that can render a specified file, an application uses the IGraphBuilder::RenderFile method. The following sections describe the basic steps taken by the Filter Graph Manager to construct the graph.

Finding the Filters

The first step in creating a filter graph is to find and create an instance of the needed filters. The filter selection process is primarily based on the media type of the data to be processed. This is because pins on two filters cannot connect unless they can agree on a common media type that they both can handle. The Filter Graph Manager can determine the media type of the data by searching the registry for the media types associated with various file name extensions (.wma, .avi, .wav, and so on). If no association exists for an extension, then the Filter Graph Manager searches for special check bytes in the file. From these check bytes the Filter Graph Manager can determine the corresponding DirectShow media type, and then begin its search for a source filter that can read it. Internally, the RenderFile logic calls IGraphBuilder::AddSourceFilter to perform this task.

All DirectShow filters are uniquely identified in the Windows registry with a GUID, along with other information, including the filter's category, the supported media types, and the filter's merit. The Filter Graph Manager examines all this information to determine which filter to create an instance of. The filter category identifies the general functionality of the filter and is used to initially narrow the search criteria. The media type information describes what kind of data the filter can accept as input, and what it can deliver as output to the next filter in the graph. The merit value indicates whether a filter should be considered during the automatic graph building process. If two filters on the system have similar media type information, the Filter Graph Manager selects the one with the highest merit value. (Some filters are purposely given a low merit value because they are designed solely for use in specialized filter graphs. Such filters must be added to a graph individually by the application.)

To search the registry, the Filter Graph Manager creates an instance of the DirectShow Filter Mapper object and calls the IFilterMapper2::EnumMatchingFilters method to perform the search. This method can enumerate DirectShow filters (as well as hardware devices, such as decoders) by category or by media type, and it returns a standard COM IEnumMoniker object containing monikers to all the filters and/or hardware devices that match the search criteria. (The use of COM monikers enables this method to query objects without actually creating an instance of them, and also to create paths and enumerate things which are not actually COM objects, such as hardware devices.)

Adding Filters to the Graph

If a moniker is found for a source filter that can read a file of the specified type, the Filter Graph Manager uses CoCreateInstance to create an instance of the filter by using the returned GUID. For most file types, the source filter will be the File Source (Async) filter. The Filter Graph Manager then calls the IFilterGraph::AddFilter method to add the filter to the filter graph. When the filter is notified that it has been added to a graph, it creates one or more output pins configured for the type of file it will be reading. After the pin(s) are created, the Filter Graph Manager examines the media type that each pin supports and then looks again in the registry for a filter that can accept that media type on its input pin. When it finds a suitable filter, it adds it to the graph.

Connecting the Filters

When the downstream filter is added, it configures its pins and is now ready to be connected to the previous (upstream) filter. The Filter Graph Manager queries each filter to determine the appropriate pins, and then calls the IGraphBuilder::Connect method to connect the output pin of the upstream filter to the input pin of the downstream filter. The two pins then perform a process of negotiation to determine the media type of the data and to determine which pin will provide the object that allocates memory for the media sample objects and the buffers that will hold the samples' data. The details of the pin connection process are discussed in more detail in the Filter Developer's Guide.

Completing the Graph

After the two pins are connected, the Filter Graph Manager examines the media type or types supported on the new filter's output pin, and searches for a filter that can handle that media type. The same process is repeated until all the necessary filters have been added to the graph. In a file playback graph, the second filter will typically be a parser or splitter filter, which reads a raw stream of data, such as AVI or MPEG, and splits it into separate audio and video streams, creating an output pin for each stream. If the data for either stream is compressed, the next filter on each stream will be a decompressor, followed by a renderer filter.

After the graph is built, the application only needs to obtain the IMediaControl interface from the Filter Graph Manager and call the IMediaControl::Run method to start playing the file.

Approach #2: The application and Filter Graph Manager share the graph building work

When your filter graph is required to do something other than simply render a file, then your application must perform at least some of the graph building work. For example, if you are building a filter graph that converts AVI files to MPEG files, you could still have the Filter Graph Manager create an AVI playback graph by calling IGraphBuilder::RenderFile, but you must then modify the graph to output to an MPEG file instead of to the monitor and speakers. This involves disconnecting and removing the video and audio renderer filters (by using IFilterGraph::Disconnect and IFilterGraph::RemoveFilter) and replacing them with MPEG video and audio compressor filters, the MPEG Multiplexer filter, and a file writer filter.

It may also be necessary to add an intermediary filter to convert the media type into one that the MPEG compressor can accept. The application can either add this intermediary filter directly, or it can use the IGraphBuilder::Connect method. When this method is called, the Filter Graph Manager will first try to connect the filters directly, and if they cannot agree on a media type, then the Filter Graph Manager will search for intermediary filters. Whenever the Filter Graph Manager searches for a filter that can handle a given media type, it will first try filters that have already been added to the graph. To prevent the Filter Graph Manager from attempting to insert intermediary filters, use the IFilterGraph::ConnectDirect method.

In the previous example, the application built the downstream portion of the graph. In other scenarios, the application might need to build the upstream portion of the graph. In that case, once you have added and connected the filters that must be added manually, you can use the IGraphBuilder::Render method to instruct the Filter Graph Manager to complete the graph automatically from the specified output pin. If a filter has two output pins, for example an audio pin and a video pin, then the application must call Render for each pin.

Approach #3: The application builds the entire graph itself

In some scenarios, your application may need to build the entire graph by individually adding and connecting each filter. In this case, you probably know specifically which filters should be added to the graph, so there is no need to use the Filter Mapper object to discover the filters. Just add each filter to the graph using the IGraphBuilder::AddFilter method and then connect them using either Connect or ConnectDirect.