Microsoft DirectX 8.1 (C++) |
This article describes how to respond to events that occur in a Microsoft® DirectShow® application. It presents a sample program that builds on the sample given in Setting the Video Window. This article does not repeat all the code from that sample; rather, it describes what you need to add, which comes to about 25 lines of new code.
How Event Notification Works
At various times while a DirectShow application is running, the status of a filter might change. For example, it might go from paused to running, encounter an error in the stream, or request that the video window be repainted. To alert the filter graph manager of a change, the filter sends an event notification, which consists of an event code that indicates the type of event, and two parameters that supply additional information. The meaning of the parameters depends on the event code. For a complete list of event codes and their parameters, see Event Notification Codes.
The filter graph manager handles some events without notifying the application, such as a request to repaint the video window. It places other events into a queue, from which the application retrieves and processes them in order. DirectShow event notification is similar in this respect to Microsoft® Windows® message queuing. In fact, you can have the filter graph manager send a Windows message to a designated window when a new event occurs. That way, the application can handle the events from within the window's message loop.
An application might need to handle a number of event codes, depending on the purpose of the application. This article focuses on two, EC_COMPLETE and EC_USERABORT.
Using Event Notification
The sample application handles events from within the main window message loop. It works by having the filter graph manager post a message when a new event occurs. The application responds by retrieving the event and taking the appropriate action.
To handle events from the main window message loop, define the message that will be sent to the application window when a new event occurs. Applications can use message numbers in the range from WM_APP through 0xBFFF as private messages, as shown in the following example:
#define WM_GRAPHNOTIFY WM_APP + 1
Next, set the filter graph manager to deliver this message to the application's main window:
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
The IMediaEventEx::SetNotifyWindow method designates the specified window (g_hwnd) as the recipient of the message. Call this method after you create the filter graph and specify the owner window, but before running the graph. For details, see the sample code at the end of this article. The IMediaEventEx interface is exposed by the filter graph manager.
When the application receives the WM_GRAPHNOTIFY message, the message's lParam parameter is equal to the third parameter passed to SetNotifyWindow. This parameter enables you to send instance data with the message. The sample code does not use this data, so it passes a value of zero. The message's wParam parameter is always zero.
In the window's WindowProc function, add a case statement for the WM_GRAPHNOTIFY message:
case WM_GRAPHNOTIFY:
HandleEvent();
break;
WM_GRAPHNOTIFY is an ordinary Windows message, and is posted separately from the DirectShow event notification queue. The sequence of events is as follows:
In the event handler function, call the IMediaEvent::GetEvent method to retrieve events from the queue:
long evCode, param1, param2;
HRESULT hr;
if (pEvent == NULL)
return;
while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
hr = pEvent->FreeEventParams(evCode, param1, param2);
if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode))
{
CleanUp();
break;
}
}
The GetEvent method retrieves the event code and the two event parameters. The last parameter to GetEvent specifies how long the method will wait for an event. Because the application calls this method in response to a WM_GRAPHNOTIFY message, the event is already queued, so use a time-out value of zero.
Because event notification and the message loop are both asynchronous, the queue might hold more than one event by the time your application responds to the message. Also, the filter graph manager can clear events from the queue, if they become invalid. Therefore, call GetEvent until it returns a failure code, indicating that the queue is empty.
If the sample program receives an EC_COMPLETE or EC_USERABORT event, it invokes the application-defined CleanUp function, which causes the application to quit gracefully. It ignores the two event parameters (param1 and param2). After you retrieve an event, call IMediaEvent::FreeEventParams to free resources associated with the event parameters. For example, the parameter may be a BSTR, whose memory was allocated by the filter graph manager.
When an EC_COMPLETE event occurs, the filter graph does not automatically switch to a stopped state. The application must stop or pause the graph. When the graph stops, filters release any resources they hold. When it pauses, they continue to hold resources. Also, when a video renderer pauses, it displays a static image of the most recent frame. The sample program stops the graph.
Clean Up
Before the application releases the IMediaEventEx pointer, it cancels event notification by calling SetNotifyWindow with a NULL pointer:
// Disable event notification before releasing the graph.
pEvent->SetNotifyWindow(NULL, 0, 0);
pEvent->Release();
pEvent = NULL;
In the WM_GRAPHNOTIFY message handler, the application checks the IMediaEventEx pointer before calling GetEvent:
if (pEvent == NULL) return;
These steps prevent a possible error, in which the application receives the event notification after it has released the pointer.
Sample Code
The following code does not include the functions WinMain or WindowProc. See the sample code in Setting the Video Window for these functions.
#include <windows.h>
#include <dshow.h>
#define WM_GRAPHNOTIFY WM_APP + 1
#define CLASSNAME "EventNotify"
IGraphBuilder *pGraph = NULL;
IMediaControl *pMediaControl = NULL;
IMediaEventEx *pEvent = NULL;
IVideoWindow *pVidWin = NULL;
HWND g_hwnd;
void PlayFile(void)
{
// Create the filter graph manager and render the file.
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
IID_IGraphBuilder, (void **)&pGraph);
pGraph->RenderFile(L"C:\\Media\\Boys.avi", NULL);
// Specify the owner window.
pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVidWin);
pVidWin->put_Owner((OAHWND)g_hwnd);
pVidWin->put_WindowStyle( WS_CHILD | WS_CLIPSIBLINGS);
// Set the owner window to receive event notices.
pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pEvent);
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
// Run the graph.
pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl);
pMediaControl->Run();
}
void CleanUp(void)
{
pVidWin->put_Visible(OAFALSE);
pVidWin->put_Owner(NULL);
// Disable event notification before releasing the graph.
pEvent->SetNotifyWindow(NULL, 0, 0);
pEvent->Release();
pEvent = NULL;
// Stop the graph.
pMediaControl->Stop();
pMediaControl->Release();
pVidWin->Release();
pGraph->Release();
PostQuitMessage(0);
}
void HandleEvent()
{
long evCode, param1, param2;
HRESULT hr;
if (pEvent == NULL) // This avoids a possible race condition when
shutting down the graph.
return;
while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
hr = pEvent->FreeEventParams(evCode, param1, param2);
if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode))
{
CleanUp();
break;
}
}
}
/* WindowProc goes here. Add the following case statement:
case WM_GRAPHNOTIFY:
HandleEvent();
break;
*/
// WinMain goes here.