## Controlling Filter Graphs Using C

This article describes how to use the interfaces and methods exposed by the Microsoft® DirectShow™ dynamic-link library to communicate with the Filter Graph Manager and the filters in a graph. These interfaces and methods render a stream of time-stamped video data in applications that are based in Microsoft Windows®. This article provides an overview of the interfaces and methods to use, and then describes their use in the DirectShow CPlay sample application.

Contents of this article:

- Interfaces that Access the Filter Graph Manager
- CPlay Tutorial
- Using the Filter Graph Manager

Interfaces that Access the Filter Graph Manager

The stream architecture enables applications to communicate with the filter graph manager, and the filter graph manager to communicate with individual filters to control the movement of data through the filter graph. It also enables filters to post events that an application can retrieve, so an application can, for example, retrieve status information about a special filter it has installed.

This section contains the following topics.

- Implementing Dual Interfaces
- Installing and Registering Quartz.dll
- Instantiating the Filter Graph Manager
- Invoking Methods on the Interfaces

An application communicates with the filter graph manager and the filters in a specific graph by using the interfaces exposed by either the filter graph manager or the filters. The following table identifies these interfaces.

Interface | Description
IAMCollection | Represents a collection of objects of type IFilterInfo, IRegFilterInfo, IMediaTypeInfo, and IPinInfo.
IBasicAudio | Controls and retrieves current volume setting.
IBasicVideo | Controls a generic video renderer.
IDeferredCommand | Used in conjunction with IQueueCommand methods to defer the execution of methods and properties.
IFilterInfo | Enables an Automation client to set and retrieve filter properties.
IGraphBuilder | Builds the filter graph manager.
IMediaControl | Instantiates the filter graph and controls media flow (runs, pauses, stops).
IMediaEvent | Enables customized event handling for events such as repainting, user termination, completion, and so on.
IMediaPosition and IMediaSeeking | Controls or retrieves start time, stop time, preroll rate, and current position.
IMediaTypeInfo | Enables an Automation client to retrieve a media type's major type and subtype.
IQueueCommand | Enables an application to queue methods and properties so that the filter invokes them during rendering of a video stream.
IPinInfo | Enables an Automation client to set and retrieve filter properties.
IRegFilterInfo | Enables an Automation client to retrieve the name of a registered filter and add a filter to the filter graph.
IVideoWindow | Controls window aspects of a video renderer, such as the window's position and size.

Of all the interfaces for the filter graph manager, C and C++ programmers use the following most effectively.

- IBasicAudio
- IBasicVideo
- IDeferredCommand
- IGraphBuilder
- IMediaControl
- IMediaEvent
- IMediaSeeking
- IQueueCommand
- IVideoWindow

The remainder are collection interfaces, which enable Automation clients, such as Microsoft Visual Basic®, to access the properties of filters, pins, and media types that are not otherwise exposed to Automation clients.

Implementing Dual Interfaces

Most of the interfaces that communicate with the Filter Graph Manager are implemented as dual interfaces. This means that an application can call the methods in each interface directly or through Automation (by using the IDispatch::Invoke method). DirectShow provides Automation support for the developer using Visual Basic. The developer using C or C++ can avoid the indirection (and accompanying overhead) associated with Automation by calling the methods directly.

DirectShow doesn't implement all interfaces as dual interfaces. An application must call the methods in these interfaces directly. For example, the following interfaces are not dual interfaces: IQueueCommand, IDeferredCommand, and IGraphBuilder.

Installing and Registering Quartz.dll

Before you begin using the filter graph manager, you must install and register the Quartz.dll dynamic-link library. Currently, the DirectShow SDK setup program automates this process. Run Setup.exe and choose the Runtime option. This program copies Quartz.dll to your Windows\System directory and adds the appropriate entries to your system's registration database.

Instantiating the Filter Graph Manager

After you have registered Quartz.dll, you can begin using the filter graph manager in your Windows-based application. First, initialize the COM library by calling the COM CoInitialize function. The sample application calls CoInitialize within its InitApplication function in the Cplay.c file of the CPlay sample application.

Next, instantiate the filter graph manager. Most applications should use the CoCreateInstance function to instantiate the filter graph. Both CoCreateInstance and CoGetClassObject can instantiate an object; however, applications typically use the former to instantiate a single object and the latter to instantiate multiple instances of an object.

The complete call to CoCreateInstance appears as follows:

    hr = CoCreateInstance( &CLSID_FilterGraph,          // Get this document's graph object.
    			  NULL,
    			  CLSCTX_INPROC_SERVER,
    			  & IID_IGraphBuilder,
    			  (void **) &media.pGraph);

The first parameter, CLSID_FilterGraph, is the class identifier (CLSID) for the filter graph manager. This CLSID is defined in the Uuids.h file, which is installed as part of the DirectShow SDK. The CLSID is a 128-bit value that the registration database uses to identify the dynamic-link library (DLL or in-process server). Using this value, COM can locate and then load the appropriate DLL.

The second parameter is a pointer to the outer IUnknown and is NULL because the Filter Graph object is not part of an aggregate.

The third parameter is the context in which the code that manages the Filter Graph will run, which is in the same process as the caller of the CoCreateInstance function.

The fourth parameter passed to the CoCreateInstance function identifies the interface that the application will use to communicate with the object. This interface identifier should be IID_IGraphBuilder; this value is defined internally in the DirectShow sources and then exposed in the Strmif.h file.

If the call to CoCreateInstance succeeds, this function returns a pointer to a filter graph manager object in the media.pGraph variable. After this pointer is returned, the application begins to call the methods in the IGraphBuilder interface. Typically, the application first calls the IGraphBuilder::RenderFile method. This method creates a filter graph for the type of file that was supplied as one of the parameters. In addition, the application can use the IGraphBuilder::QueryInterface method to retrieve pointers to any of the interfaces exposed by the filter graph manager. The IGraphBuilder interface derives from IUnknown.

If you are writing your application in C (rather than C++), you must use a vtable pointer to call the methods exposed by IGraphBuilder. The following example illustrates a call to the QueryInterface method on the IGraphBuilder interface from within an application written in C.

    hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph,
        &IID_IMediaEvent, (void **) &pME);

If you are writing your application in C++, the function is simpler; it requires less indirection and one less parameter:

    hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void **) &pME);

Invoking Methods on the Interfaces

An application can retrieve a pointer to any of the other interfaces exposed by the filter graph manager by calling the IGraphBuilder::QueryInterface method and supplying a REFIID for the corresponding interface. After retrieving this interface pointer, the application can begin calling the interface's methods by using the interface's vtable pointer (just as the IGraphBuilder's vtable pointer called the IGraphBuilder::QueryInterface method in the previous example). The application must release an acquired interface by calling the IUnknown::Release method on that interface.

CPlay Tutorial

This section's tutorial describes CPlay, a sample included in the DirectShow SDK that plays a media file. The source files for this application are in the Samples\DS\Player\CPlay subdirectory of the DirectShow SDK project.

This section contains the following topics.

- CPlay Sample Application
- Files in CPlay

This tutorial does not describe the Microsoft Windows® API code found in the source files. Instead, it focuses almost exclusively on the code that shows:

- How to instantiate a filter graph for a particular file type.
- How to process media events.
- How to run, pause, and stop the media stream.
- How to set a global variable to indicate the valid media state (running, paused, or stopped).
- How to release the resources and clean up the variables used by the filter graph.

CPlay Sample Application

You can use the CPlay sample application to open a media file and then run, pause, or stop the corresponding media stream. The application's user interface consists of menus and a toolbar. The menus include File, Media, and Help. The toolbar includes Play, Pause, and Stop buttons.

After you open a file and click Play, the filter graph renders the video stream in its default window.

Files in CPlay

The sample application consists of six source files. Each file contains source code that accomplishes a specific set of tasks. For example, the About.c module contains the code that displays the About dialog box. The following table identifies each source file and describes its purpose.

File | Description
About.c | Displays the About dialog box.
Assert.c | Displays a message box with debugging information.
Cplay.c | Processes user input.
File.c | Displays the File Open dialog box.
Media.c | Instantiates the filter graph; invokes the filter graph methods to run, pause, and stop the video rendering.
Toolbar.c | Draws the toolbar buttons.

The remainder of this article focuses primarily on the code found in the Media.c file; however, references to other files appear when describing some of the tasks accomplished by this application.

Using the Filter Graph Manager

The Media.c file contains initialization, destruction and cleanup, command handling, and state change code. The initialization code instantiates a filter graph for a particular file type. The destruction code releases the resources and cleans up the variables used by the filter graph. The command handling code invokes the methods required to play, pause, or stop the video rendering. The state change code sets a global variable that indicates valid media states (that is, can stop, can pause, can play).

This section contains the following topics.

- Initializing the Filter Graph Manager and the Filter Graph
- Playing, Pausing, and Stopping the Video Stream
- Handling Events

Initializing the Filter Graph Manager and the Filter Graph

The following code illustrates how to create the filter graph manager and the filter graph, including including how to enable event handling, and how to open the media file that the filter graph will render.

First, instantiate the filter graph manager. The CreateFilterGraph function in Media.c instantiates the filter graph manager by calling the COM CoCreateInstance function. It saves the pointer returned by CoCreateInstance in the pGraph member of a global media structure (defined in Media.h in the CPlay sample included in the SDK).

    BOOL CreateFilterGraph()
    {
        HRESULT hr;

        hr = CoCreateInstance(&CLSID_FilterGraph,  // CLSID of object
               NULL,                         // Outer unknown.
               CLSCTX_INPROC_SERVER,         // Type of server.
               &IID_IGraphBuilder,           // Interface wanted.
    	       (void **) &media.pGraph);     // Pointer to IGraphBuilder.
    ...
    }

Next, enable event handling. Using the pointer returned by CoCreateInstance, the CreateFilterGraph function retrieves a pointer to the IMediaEvent interface by calling the IUnknown::QueryInterface method. The interface pointer retrieves an event notification handle by calling the IMediaEvent::GetEventHandle method. The main message loop uses this handle (the DoMainLoop function in CPlay.c). After GetEventHandle obtains the handle, CreateFilterGraph releases the pointer to the IMediaEvent interface by calling the IUnknown::Release method.

    IMediaEvent *pME;

    hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph, &IID_IMediaEvent, (void **) &pME);
        if (FAILED(hr)) {
    	  DeleteContents(); //Releases the pointer media.pGraph.
    	  return FALSE;
        }

        hr = pME->lpVtbl->GetEventHandle(pME, (OAEVENT*) &media.hGraphNotifyEvent);
        pME->lpVtbl->Release( pME );

After instantiating the Filter Graph Manager and enabling event handling, open the media file to be rendered. In the CPlay sample application, a user opens a multimedia file. The file name extension (for example, .avi or .mpg) is unimportant, because the DirectShow filter graph examines the file header to ensure that the file is a multimedia file.

When the user opens a file by choosing Open from the File menu, this action calls the OpenMediaFile function in Media.c, which displays the File Open common dialog box.

     void OpenMediaFile( HWND hwnd, LPSTR szFile ){
    // File..Open has been selected
        static char szFileName[ _MAX_PATH ];
        static char szTitleName[ _MAX_FNAME + _MAX_EXT ];
        // The user has already chosen a file.
        if( szFile!=NULL && RenderFile( szFile ) ){
            LPSTR szTitle;

            // Work out the full path name and the file name from the
            // specified file.
            GetFullPathName( szFile, _MAX_PATH, szFileName, &szTitle );
            strncpy( szTitleName, szTitle, _MAX_FNAME + _MAX_EXT );
            szTitleName[ _MAX_FNAME + _MAX_EXT -1 ] = '\0';

            // Set the main window title and update the state.
            SetTitle( hwnd, szTitleName );
            ChangeStateTo( Stopped );
    // The user hasn't already chosen a file, so display the Open File
    // dialog box. The DoFileOpenDialog function is in file.c in the CPlay
    // sample.
        } else if( DoFileOpenDialog( hwnd, szFileName, szTitleName )
                   && RenderFile( szFileName ) ){

            // Set the main window title and update the state.
            SetTitle( hwnd, szTitleName );
            ChangeStateTo( Stopped );
        }
    }

After the file has been opened, render the file. The OpenMediaFile function passes the name of the user's chosen file to the RenderFile function in Media.c. The RenderFile function in turn calls the CreateFilterGraph function to instantiate the filter graph manager. After creating the filter graph manager, the RenderFile function calls the IGraphBuilder::RenderFile method to create the actual filter graph:

    BOOL RenderFile( LPSTR szFileName )
    {
        HRESULT hr;
        WCHAR wPath[MAX_PATH];
        DeleteContents(); // Release the pointer media.pGraph if it exists,
                          // because the call to CreateFilterGraph will
                          // retrieve a new pointer.

       //Create the filter graph manager
        if ( !CreateFilterGraph() ) {
    	  PlayerMessageBox( IDS_CANT_INIT_QUARTZ );
    	  return FALSE;
        }

        MultiByteToWideChar( CP_ACP, 0, szFileName, -1, wPath, MAX_PATH );
        SetCursor( LoadCursor( NULL, IDC_WAIT ) ); // Put up the hour-glass
                                                   // while the media file
                                                   // loads.
        // Create the actual filter graph
        hr = media.pGraph->lpVtbl->RenderFile(media.pGraph, wPath, NULL);
        SetCursor( LoadCursor( NULL, IDC_ARROW ) ); // Turn the cursor back
                                                    // to an arrow.
        if (FAILED( hr )) {
    	  PlayerMessageBox( IDS_CANT_RENDER_FILE );
    	  return FALSE;
        }
        return TRUE;

    }

Playing, Pausing, and Stopping the Video Stream

After the application creates the filter graph manager and the filter graph, it can expose the user interface, which enables the user to play, pause, and stop video rendering. In the case of CPlay, the toolbar buttons (Play, Pause, and Stop) are redrawn in color after the user chooses a valid file.

When the user clicks Play, the OnMediaPlay function is called. This function accomplishes the following tasks sequentially.

1. Examines the global state variable in the media structure to ensure that the video can be rendered.
2. Retrieves a pointer to the IMediaControl interface.
3. Invokes the IMediaControl::Run method.
4. Releases the IMediaControl interface.
5. Sets the global state variable.

The OnMediaPlay function appears as follows:

    void OnMediaPlay( void ){
        if( CanPlay() ){
            HRESULT   hr;
            IMediaControl *pMC;

            // Obtain the interface to our filter graph.
            hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph,
                &IID_IMediaControl, (void **) &pMC);

            if( SUCCEEDED(hr) ){
                // Ask the filter graph to play and release the interface.
                hr = pMC->lpVtbl->Run( pMC );
                pMC->lpVtbl->Release( pMC );

                if( SUCCEEDED(hr) ){
                    ChangeStateTo( Playing );
                    return;
                }
            }

            // Inform the user that an error occurred.
            PlayerMessageBox( IDS_CANT_PLAY );

        }
    }

The code that handles pausing and stopping the video stream is nearly identical to the code that plays the media stream. The actual functions that handle these tasks are OnMediaPause and OnMediaStop, respectively. You can find all this code in the Media.c file.

Handling Events

The IMediaEvent interface enables an application to receive events that the filter graph or individual filters within the graph raise. Following are some of the possible events and corresponding event notification messages.

Event notification message | Description
EC_COMPLETE | The video has finished rendering.
EC_USERABORT | A user forced the termination of a requested operation.
EC_ERRORABORT | An error forced the termination of a requested operation.
EC_PALETTE_CHANGED | The video palette changed.
EC_REPAINT | The display should be repainted.

The sample application tracks the EC_COMPLETE, EC_USERABORT, and EC_ERRORABORT events by using the IMediaEvent::GetEvent method. The application calls this method from within the OnGraphNotify function. The application calls the OnGraphNotify function (in Media.c) from within the application's main message loop function (DoMainLoop), which you can find in the Cplay.c file.

If any of these events are raised, OnGraphNotify immediately stops video rendering by calling the OnMediaStop function.

The OnGraphNotify function accomplishes the following tasks sequentially.

1. Declares the IMediaEvent interface pointer and the variables for the event code and event parameters.
2. Retrieves a pointer to the IMediaEvent interface by calling IUnknown::QueryInterface.
3. Calls the IMediaEvent::GetEvent method to retrieve the next event notification. The retrieved event is stored in the lEventCode variable and the event parameters are stored in the lParam1 and lParam2 variables. The time-out value is set to zero, which means that GetEvent will not wait for an event to occur, but only return an already waiting event.
4. Checks the event type stored in lEventCode and takes the appropriate action, if GetEvent retrieves an event. See Event Notification Codes for a list of the system-supplied events that DirectShow supports. Note that if the event parameters are declared as type BSTR instead of LONG, IMediaEvent::FreeEventParams should be called free the BSTRs.

    void OnGraphNotify(void) {
        IMediaEvent *pME;

        long lEventCode, lParam1, lParam2;

        ASSERT( media.hGraphNotifyEvent != NULL );

        if( SUCCEEDED(media.pGraph->lpVtbl->QueryInterface(media.pGraph,
                &IID_IMediaEvent, (void **) &pME))){
            if( SUCCEEDED(pME->lpVtbl->GetEvent(pME, &lEventCode, &lParam1,
                    &lParam2, 0))
                && ( lEventCode == EC_COMPLETE
                    || lEventCode == EC_USERABORT
                    || lEventCode == EC_ERRORABORT
                    )
              )
                OnMediaStop();
                  pME->lpVtbl->Release( pME );
                  }
    }

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