DSBuild: Building a Filter Graph (Mostly) Manually

DSBuild: Building a Filter Graph (Mostly) Manually

The DSBuild application does most of the heavy lifting involved in creating an audio player for a wide variety of audio formats essentially any audio format supported by DirectShow, including the audio tracks of AVI and Windows Media movies. The application code creates a Filter Graph Manager object and then creates two filters: a source filter (which points to a disk file) and an audio renderer filter. Then, using the Intelligent Connect capability of the Filter Graph Manager, the application connects the output pin of the source filter to the input pin of the audio renderer, adding the necessary intermediate transform filters to provide a path between source and renderer. Once that path has been created, the filter graph begins execution, plays the media file until completion, and then stops.

Examining main, Again

The source code in file DSBuild.cpp has four functions, and three of these are nearly identical to their counterparts in DSRender.cpp. The extra function, GetPin, will be examined in detail in the section Locating Pins and GetPin a bit further along. We need to begin with a detailed examination of main, which initially looks a lot like the version in DSRender.cpp.

// DSBuild implements a very simple program to render audio files // or the audio portion of movies. // int main(int argc, char* argv[]) { IGraphBuilder *pGraph = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; IBaseFilter *pInputFileFilter = NULL; IBaseFilter *pDSoundRenderer = NULL; IPin *pFileOut = NULL, *pWAVIn = NULL; // Get the name of an audio or movie file to play. if (!GetMediaFileName()) { return(0); } // Initialize the COM library. HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { printf("ERROR - Could not initialize COM library"); return hr; } // Create the Filter Graph Manager object and retrieve its // IGraphBuilder interface. hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); if (FAILED(hr)) { printf("ERROR - Could not create the Filter Graph Manager."); CoUninitialize(); return hr; } // Now get the media control interface... hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); if (FAILED(hr)) { pGraph->Release(); CoUninitialize(); return hr; } // And the media event interface. hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); if (FAILED(hr)) { pControl->Release(); pGraph->Release(); CoUninitialize(); return hr; } // Build the graph. // Step one is to invoke AddSourceFilter // with the file name we picked out earlier. // Should be an audio file (or a movie file with an audio track). // AddSourceFilter instantiates the source filter, // adds it to the graph, and returns a pointer to the filter's // IBaseFilter interface. #ifndef UNICODE WCHAR wFileName[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, g_PathFileName, -1, wFileName, MAX_PATH); hr = pGraph->AddSourceFilter(wFileName, wFileName, &pInputFileFilter); #else hr = pGraph->AddSourceFilter(wFileName, wFileName, &pInputFileFilter); #endif if (SUCCEEDED(hr)) { // Now create an instance of the audio renderer // and obtain a pointer to its IBaseFilter interface. hr = CoCreateInstance(CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&pDSoundRenderer); if (SUCCEEDED(hr)) { // And add the filter to the filter graph // using the member function AddFilter. hr = pGraph->AddFilter(pDSoundRenderer, L"Audio Renderer"); if (SUCCEEDED(hr)) { // Now we need to connect the output pin of the source // to the input pin of the renderer. // Obtain the output pin of the source filter. // The local function GetPin does this. pFileOut = GetPin(pInputFileFilter, PINDIR_OUTPUT); if (pFileOut != NULL) { // Is the pin good? // Obtain the input pin of the WAV renderer. pWAVIn = GetPin(pDSoundRenderer, PINDIR_INPUT); if (pWAVIn != NULL) { // Is the pin good? // Connect the pins together: // We use the Filter Graph Manager's // member function Connect, // which uses Intelligent Connect. // If this fails, DirectShow couldn't // render the media file. hr = pGraph->Connect(pFileOut, pWAVIn); } } } } } if (SUCCEEDED(hr)) { // Run the graph. hr = pControl->Run(); if (SUCCEEDED(hr)) { // Wait for completion. long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); // Note: Do not use INFINITE in a real application // because it can block indefinitely. } hr = pControl->Stop(); } // Before we finish, save the filter graph to a file. SaveGraphFile(pGraph, L"C:\\MyGraph.GRF"); // Now release everything we instantiated-- // that is, if it got instantiated. if(pFileOut) { // If it exists, non-NULL pFileOut->Release(); // Then release it } if (pWAVIn) { pWAVIn->Release(); } if (pInputFileFilter) { pInputFileFilter->Release(); } if (pDSoundRenderer) { pDSoundRenderer->Release(); } pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); return 0; }

The opening lines of the function are essentially the same as those from DSRender. A Filter Graph Manager object is instantiated through a COM call, and subsequent QueryInterface calls return pointers to its IMediaControl and IMediaEvent interfaces. That s everything needed to begin building the filter graph. At this point, we use a new method of IGraphBuilder, AddSourceFilter, which takes a file name as a parameter and returns a pointer to an IBaseFilter interface on the filter that was chosen and instantiated. The IBaseFilter interface is exposed by all DirectShow filters.

Next the audio renderer filter is created using CoCreateInstance, with a class ID value of CLSID_DSoundRender, which returns the IBaseFilter interface for that object. Once that filter has been created successfully, it is added to the filter graph with the ingeniously named IGraphBuilder method AddFilter. The AddFilter method takes two parameters. The first parameter is a pointer to the IBaseFilter interface on the filter to be added, while the second parameter is an application-defined string used to identify the filter. (You can use this string to name the filter whatever you like. This feature is particularly worthwhile when examining a filter graph in GraphEdit.)

Now we have two filters in the filter graph: a source filter pointing to the file and an audio output filter. They need to be connected together, probably through a path of transform filters. The transform filters required to connect source to renderer will vary by media type of the source file. Rather than examining the source file ourselves to determine what intermediate filters are needed (which would be a long and involved process), we ll use the DirectShow Intelligent Connect feature to do the work for us.

To begin, we ll need to obtain IPin interfaces which, as the name suggests, are exposed by the pins on a filter for both the output of the source filter and the input of the renderer. We use the local function GetPin (explained in detail in the next section) to obtain these interfaces on the pins we want to connect. Once we have both of these, we can invoke the IGraphBuilder method Connect. (Connect takes as parameters two pins; if successful, the method connects the two pins through some set of intermediate filters.) If the call to Connect fails, DirectShow wasn t able to build a path between source and renderer, possibly because the media type of the source file isn t supported by DirectShow or because the file didn t contain any audio.

As in DSRender, the application uses the IMediaControl interface s Run method to begin execution of the filter graph, and the IMediaEvent method WaitForCompletion pauses execution of the application until the media file has been completely rendered. At this point, the Stop method is called and the filter graph halts its execution. The filter graph is written to a file with a call to Save GraphFile, the allocated interfaces are released, and the application terminates.

Even when created by hand, a filter graph isn t a difficult object to build or maintain. However, this application would have been significantly more difficult to write without Intelligent Connect, which allowed us to ignore the specifics of the media in the source file.

Locating Pins and GetPin

The local function GetPin allows us to locate input and output pins on a filter and retrieve the IPin interface that allows us to control the pins. The code for the function is concise, as shown here:

// This code allows us to find a pin (input or output) on a filter. IPin *GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir) { BOOL bFound = FALSE; IEnumPins *pEnum; IPin *pPin; // Begin by enumerating all the pins on a filter HRESULT hr = pFilter->EnumPins(&pEnum); if (FAILED(hr)) { return NULL; } // Now look for a pin that matches the direction characteristic. // When we've found it, we'll return with it. while(pEnum->Next(1, &pPin, 0) == S_OK) { PIN_DIRECTION PinDirThis; pPin->QueryDirection(&PinDirThis); if (bFound = (PinDir == PinDirThis)) break; pPin->Release(); } pEnum->Release(); return (bFound ? pPin : NULL); }

The IBaseFilter interface has a member function, EnumPins, which returns an IEnumPins interface. This interface enables you to iterate through a list of all the pins on a filter. Each element in the IEnumPins list contains an IPin object. As the code walks through this list of pins, each pin is queried through an invocation of its IPin::QueryDirection method. If the direction matches the requirements, that IPin interface pointer becomes the function s return value with one caveat: some filters have multiple input and output pins, and these pins can have different media types, so you can t know that a returned IPin will be useful in every situation. You could call GetPin on a digital video filter, expecting to get an output pin for digital video, only to find that it won t connect to a video renderer because the output pin is for the audio track that accompanies the video. This function doesn t discriminate.



Programming Microsoft DirectShow for Digital Video and Television
Programming Microsoft DirectShow for Digital Video and Television (Pro-Developer)
ISBN: 0735618216
EAN: 2147483647
Year: 2002
Pages: 108
Authors: Mark D. Pesce

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net