## Play a Movie in a Window Using DirectDrawEx and Multimedia Streaming

This article walks through the MovieWin C++ example code, which plays movies in a window by rendering to a Microsoft DirectDraw® surface. The MovieWin example code is a Microsoft® Windows® 95 application that is an extension of the ShowStrm Sample (Multimedia Streaming Application) sample. MovieWin uses multimedia streaming to render a video file to a DirectDraw surface created through DirectDrawEx. It implements a primary DirectDraw surface and an offscreen DirectDraw surface to optimize frame blitting. It also attaches a DirectDraw clipper to the window to process window overlapping.

Contents of this article:

- Necessary Header files and Libraries
- WinMain Function
- Initialize DirectDraw Surfaces and Create the Clipper
- Open a Movie File
- Create the Multimedia Stream Object
- Create the Stream Sample Object
- Render the Multimedia Stream to the DirectDraw Surface
- Release Objects
- WndMainProc Function
- Entire MovieWin Example Code

The example demonstrates a way to render a movie that differs from the traditional method of instantiating a filter graph directly in your application. The MovieWin example code uses the multimedia streaming interfaces to automatically negotiate the transfer and conversion of data from the source to the application, so you don't have to write code to handle the connection, transfer of data, data conversion, or actual data rendering.

Additionally, the example demonstrates how to create DirectDraw surfaces and how to add code for a DirectDrawClipper object through DirectDrawEx.

Note that all error checking has been left out of the code walk-through. The Entire MovieWin Example Code section provides all of the code with complete error checking.

Necessary Header files and Libraries

This section discusses necessary headers and libraries that need to be included and examines each function in the MovieWin example code in detail.

To compile the MovieWin example code you must have DirectX Media SDK 5.x or later installed and you will need to set your include path under Tools/Options/Directories/Include to c:\DXMedia\Include and your library path to c:\DXMedia\Lib. Also link with the Amstrmid.lib, the Quartz.lib, the Strmbase.lib, and the Ddraw.lib (DirectDrawEx does not provide its own library) libraries under Project/Settings/Link.

Include the necessary header files and define the window's name and the window class name.

    #include <windows.h>
    #include <mmstream.h>	// Multimedia stream interfaces
    #include <amstream.h>	// DirectShow multimedia stream interfaces
    #include <ddstream.h>	// DirectDraw multimedia stream interfaces
    #include <initguid.h>   // Defines DEFINE_GUID macro and enables GUID initialization
    #include <ddrawex.h>	// DirectDrawEx interfaces
    #include "resource.h"   // Resources for the menu bar

    #define APPLICATIONNAME "Multimedia Stream In Window"
    #define CLASSNAME "MMSDDRAWEXWINDOW"

Then declare the following global variables:

    HWND              ghWnd;
    HINSTANCE         ghInst;
    BOOL              g_bAppactive=FALSE,    // The window is active
                      g_bFileLoaded = FALSE, // There is a file loaded
                      g_bPaused=FALSE;       // The movie has been paused
    RECT              rect, rect2;          // Rectangles for screen coordinates

The ghWnd variable is the handle of the window to send messages to. The ghInst variable is the handle of the instance of the window. The three Boolean values g_bAppactive, g_bFileLoaded, g_bPaused variables are used to determine the various states of the application and are used extensively by the WndMainProc function. They are declared as global variables to retain their TRUE or FALSE status. Finally, rect and rect2 are rectangle structures that will contain the original movie coordinates and the coordinates of the window to show the movie in, respectively.

Next, declare the DirectDrawEx and multimedia streaming interfaces. The reference count of the interfaces is automatically incremented on initialization, so you don't need to call the IUnknown::AddRef method to increment them. For more information on these interfaces, see DirectDrawEx, , and the Microsoft DirectX® SDK.

    //DirectDrawEx Global interfaces
    IDirectDraw            *g_pDD=NULL;
    IDirectDraw3           *g_pDD3=NULL;
    IDirectDrawFactory     *g_pDDF=NULL;
    IDirectDrawSurface     *g_pPrimarySurface=NULL,
                           *g_pDDSOffscreen=NULL;
    IDirectDrawClipper     *g_pDDClipper=NULL;

    //Global MultiMedia streaming interfaces
    IMultiMediaStream        *g_pMMStream=NULL;
    IMediaStream             *g_pPrimaryVidStream=NULL;
    IDirectDrawMediaStream   *g_pDDStream=NULL;
    IDirectDrawStreamSample  *g_pSample=NULL;

Finally, declare the function prototypes.

    //Function prototypes
    int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow);
    LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    HRESULT InitDDrawEx();
    BOOL GetOpenMovieFile(LPSTR szName);
    HRESULT RenderFileToMMStream(LPCTSTR szFilename);
    HRESULT InitRenderToSurface();
    void RenderToSurface();
    void ExitCode();

WinMain Function

The WinMain function is a generic Windows function with a few exceptions.

Immediately after the Win32 CreateWindowEx function, the InitDDrawEx function is called to initialize the DirectDrawEx surfaces that the movie will play on and to create a clipper to attach to the window. The clipper can only be created after it has a global handle to the window (ghWnd), and so must be created after the call to the CreateWindowEx function has returned.

The message pump is a standard Windows message pump containing the TranslateMessage and the DispatchMessage functions with an interesting note. Before the code reaches these functions, it calls the PeekMessage function. The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure. If there are messages being passed to the window the code proceeds to the regular GetMessage, TranslateMessage, and DispatchMessage functions respectively. However if there are no messages in the message queue, the process will check for the g_bFileLoaded Boolean value, which specifies whether a file has been loaded. Initially, the value in g_bFileLoaded is FALSE so the code maintains its loop, waiting for new messages.

After a file has been loaded and rendered to a multimedia stream (see GetOpenMovieFile function and RenderFileToMMStream function) the g_bFileLoaded value and the g_bAppactive values are set to TRUE and the message pump will call the RenderToSurface function, which blits one frame of the movie to the window's coordinates. As the loop continues, the movie continues to render frame by frame until completion or until it is interrupted the PeekMessage function with an outside message to the window. If the movie is paused, stopped, or if it completes on its own, the g_bAppactive variable is set to FALSE, which causes the call to RenderToSurface to be skipped until g_bAppactive is set to TRUE again.

The following code shows how to create the message pump.

    while(1){
    		//The PeekMessage function checks a thread message queue
    		//for a message and places the message (if any) in the specified structure.
    		if(PeekMessage(&msg, NULL, 0,0,PM_NOREMOVE)){

    			// Quit if WM_QUIT found
    			if(!GetMessage(&msg,NULL, 0, 0)) return (msg.wParam);

    			// Otherwise handle the messages
    				TranslateMessage(&msg);		// Allow input
    				DispatchMessage(&msg);		// Send to appropriate process.
    		}
    		else{
    			// If there are no other windows messages...
    			// Render frame by frame (but only if the App is the active
    			// window and a file is actually loaded)
    			if (g_bFileLoaded && g_bAppactive) {
    				RenderToSurface();
    				}
    			}
    		}
    	  return msg.wParam;

Initialize DirectDraw Surfaces and Create the Clipper

The InitDDrawEx function initializes a primary DirectDraw surface and an offscreen DirectDraw surface, as well as a clipper object that is attached to the window. The following code shows how to do this.

1. Declare local variables and initialize the COM subsystem.

       HRESULT			hr=NOERROR;
       DDSURFACEDESC	ddsd, ddsd2;

       CoInitialize(NULL);

2. Create the DirectDrawFactory object and expose the IDirectDrawFactory interface.

       CoCreateInstance(CLSID_DirectDrawFactory, NULL, CLSCTX_INPROC_SERVER,
       							IID_IDirectDrawFactory, (void **)&g_pDDF);

   Use the pointer to the IDirectDrawFactory interface to call the IDirectDrawFactory::CreateDirectDraw method, which you use to create the DirectDraw object, set the cooperative level, and get the address of an IDirectDraw interface pointer.

       g_pDDF->CreateDirectDraw(NULL, GetDesktopWindow(), DDSCL_NORMAL,
       				NULL, NULL, &g_pDD);

3. Query for the IDirectDraw3 interface, which you use to create the DirectDraw surfaces.

       g_pDD->QueryInterface(IID_IDirectDraw3, (LPVOID*)&g_pDD3);

4. Initialize the DDSURFACEDESC structure for the primary surface. The following is the minimum code needed to accomplish this. You should also initialize other members of the structure here if your code must create more sophisticated applications.

       ZeroMemory(&ddsd, sizeof(ddsd));
       ddsd.dwSize = sizeof(ddsd);
       ddsd.dwFlags = DDSD_CAPS;
       ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

5. Call the IDirectDraw3::CreateSurface method to create the primary DirectDraw surface and return a pointer to IDirectDrawSurface interface.

       g_pDD3->CreateSurface(&ddsd, &g_pPrimarySurface, NULL);

6. Create the offscreen surface where the IStreamSample::Update method will send the individual movie frames before they are blitted onto the screen. Using an offscreen surface optimizes the performance of the video and enables the blits to be processed at a faster rate. Also the video remains in memory and can be called upon in the event of a repaint notification.

   You must create the offscreen surface with the identical height, width, and pixel format to the primary surface in order to blit from one to the other. Do this by first getting the DDSURFACEDESC structure from the primary surface through a call to the IDirectDrawSurface::GetSurfaceDesc method.

       g_pPrimarySurface->GetSurfaceDesc(&ddsd);

7. Now you can initialize the DDSURFACEDESC structure for the offscreen surface with the same parameters as the primary surface:

       ZeroMemory(&ddsd2, sizeof(ddsd2));
       ddsd2.dwSize = sizeof(ddsd2);
       ddsd2.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
       ddsd2.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
       ddsd2.dwHeight = ddsd.dwHeight;	//set the height of the surfaces equal
       ddsd2.dwWidth  = ddsd.dwWidth;	//set the width of the surfaces equal
       ddsd2.ddpfPixelFormat = ddsd.ddpfPixelFormat; //set the pixel formats equal

8. Call the IDirectDraw3::CreateSurface method to create the offscreen surface.

       g_pDD3->CreateSurface(&ddsd2, &g_pDDSOffscreen, NULL)

   At this point, you should have two identical DirectDraw surfaces: the offscreen surface that will be used to update the movie frames on, and the primary surface, which your user will see. The primary surface will contain the video after the data has been blitted from the offscreen surface to the primary surface.

9. To give the window the look and feel of a regular window, you must add code for a clipper. The DirectDrawClipper object (casually referred to as a "clipper") helps you prevent blitting to certain portions of a surface or beyond the bounds of a surface. DirectDrawClipper objects expose their functionality through the IDirectDrawClipper interface. You can create a clipper by calling the IDirectDraw3::CreateClipper method.

   Use the following code to create the clipper object and retrieve a pointer to the IDirectDrawClipper interface.

       g_pDD3->CreateClipper(0, &g_pDDClipper, NULL);

10. Use the IDirectDrawSurface interface to attach the clipper to the primary surface.

        g_pPrimarySurface->SetClipper(g_pDDClipper);

11. Finally, associate the clipper with the window by calling the IDirectDrawClipper::SetHWnd method.

        g_pDDClipper->SetHWnd(0, ghWnd);

At this point, you should have two DirectDraw surfaces, and a clipper attached to the primary surface and to the applications window. The DirectDrawEx initialization is complete and all the objects are available to the process until the ExitCode function is called to release the objects.

For more information on DirectDrawEx, see DirectDrawEx.

Open a Movie File

The following code shows how to use the GetOpenMovieFile function to display the Open file dialog box. It initializes the OPENFILENAME structure and calls the GetOpenFileName API.

    BOOL GetOpenMovieFile(LPSTR szName)
    {
    	OPENFILENAME	ofn;

    	ofn.lStructSize       = sizeof(OPENFILENAME);
    	ofn.hwndOwner         = ghWnd;
    	ofn.lpstrFilter       = NULL;
    	ofn.lpstrFilter       = "Video (*.avi;*.mpg;*.mpeg)\0*.avi;*.mpg;*.mpeg\0All Files (*.*)\0*.*\0";
    	ofn.lpstrCustomFilter = NULL;
    	ofn.nFilterIndex      = 1;
    	*szName = 0;
    	ofn.lpstrFile         = szName;
    	ofn.nMaxFile          = MAX_PATH;
    	ofn.lpstrInitialDir   = NULL;
    	ofn.lpstrTitle        = NULL;
    	ofn.lpstrFileTitle    = NULL;
    	ofn.lpstrDefExt       = NULL;
    	ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;
    	return GetOpenFileName((LPOPENFILENAME)&ofn);
    }

Create the Multimedia Stream Object

The RenderFileToMMStream function creates a multimedia stream and attaches the stream to the file retrieved by the GetOpenMovieFile function. This function uses the IAMMultiMediaStream interface to expose DirectShow functionality to the application. After the address of a pointer to the IAMMultiMediaStream interface is retrieved, it will be used to initialize the stream, add specific media streams to the current filter graph, and open and automatically create a filter graph for the specified media file.

The following steps show how to do this.

1. Declare the local variables hr and pAMStream, and convert the provided file name to a wide (Unicode) string.

       HRESULT hr;
       IAMMultiMediaStream *pAMStream=NULL;
       WCHAR wFile[MAX_PATH];
       MultiByteToWideChar(CP_ACP, 0, szFilename, -1, wFile,
       			sizeof(wFile)/sizeof(wFile[0]));

2. Create the AMMultiMediaStream object and initialize it.

       hr =CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER,
                                             IID_IAMMultiMediaStream, (void **)&pAMStream);
       hr = pAMStream->Initialize(STREAMTYPE_READ, 0, NULL);

3. Now that you have a stream object, add a single audio and video stream to it; typically, you need only these two streams for media file playback. When the IAMMultiMediaStream::AddMediaStream method receives the MSPID_PrimaryVideo flag as its second parameter, it uses the pointer in the first parameter as the destination surface for video playback. The audio stream needs no such surface, however, so pass NULL as the first parameter when you add audio streams. The AMMSF_ADDDEFAULTRENDERER flag automatically adds the default sound renderer to the current filter graph.

       hr = pAMStream->AddMediaStream(g_pDD3, &MSPID_PrimaryVideo, 0, NULL);
       hr = pAMStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, AMMSF_ADDDEFAULTRENDERER, NULL);

4. Finally, open and create a filter graph for the specified media file and save the local stream to the global variable g_pMMStream. Don't forget to increase the reference count on the IAMMultiMediaStream object.

       //Opens and automatically creates a filter graph for the specified media file
       hr = pAMStream->OpenFile(wFile, 0);
       //save the local stream to the global variable
       g_pMMStream = pAMStream;
       // Add a reference to the file
       pAMStream->AddRef();

Now that you have valid streams and a pointer to them, this function is complete. For more information on multimedia streams see and Use Multimedia Streaming in DirectShow Applications.

Create the Stream Sample Object

The InitRenderToSurface function creates the stream sample that will be associated with the offscreen DirectDrawSurface object. The stream sample will be used later by the RenderToSurface function to call the IStreamSample::Update method to perform frame-by-frame updates of the sample.

The following steps show how to do this.

1. To create and initialize the stream sample, declare the local variables, and then get the primary video media stream by using the IMultiMediaStream::GetMediaStream method.

       HRESULT			hr;
       DDSURFACEDESC	ddsd;

       //Use the multimedia stream to get the primary video media stream
       hr = g_pMMStream->GetMediaStream(MSPID_PrimaryVideo, &g_pPrimaryVidStream);

2. After you obtain the primary video stream interface (IMediaStream), you can use it to query for the IDirectDrawMediaStream interface, which you'll use to create the stream sample.

       hr = g_pPrimaryVidStream->QueryInterface(IID_IDirectDrawMediaStream, (void **)&g_pDDStream);

3. Before you can create the stream sample, you must call the IDirectDrawMediaStream::GetFormat method. The trick to watch on this call is that you must set the dwSize member of the DDSURFACEDESC structure. After the stream sample has retrieved the height and width of the movie file, you can set the rectangle that the offscreen surface will use to contain the video data.

       ddsd.dwSize = sizeof(ddsd);
       hr = g_pDDStream->GetFormat(&ddsd, NULL, NULL, NULL);
       rect.top = rect.left = 0;
       rect.bottom = ddsd.dwHeight;
       rect.right = ddsd.dwWidth;

4. Create the stream sample by calling the IDirectDrawMediaStream::CreateSample method with the offscreen surface and the RECT structure, which was just initialized with the movie coordinates. This method will retrieve a pointer to the global IDirectDrawStreamSample interface g_pSample.

       hr = g_pDDStream->CreateSample(g_pDDSOffscreen, &rect, 0, &g_pSample);

At this point, the IDirectDrawMediaStream::CreateSample method has created a global IDirectDrawStreamSample stream sample and returned a pointer to g_pSample, its interface, which the RenderToSurface function can use.

Render the Multimedia Stream to the DirectDraw Surface

The RenderToSurface function handles the actual rendering and blits the video stream's data to the primary surface. The main message pump in the WinMain function calls this method. The RenderToSurface function performs one individual frame update at a time and one blit from the offscreen surface to the primary surface. When the movie is complete, it will set the stream state to STOP.

The following steps show how to do this.

1. Declare the local variables.

       HRESULT		hr;
       POINT		point;

2. Call the IStreamSample::Update method. Each loop iteration throws out the previous video image and grabs the next image from the stream.

   If the update is successful, the Microsoft Win32® GetClientRect and the ClientToScreen functions are called to get the rectangle coordinates of the window into which the video will be displayed. These functions must be called after each update, in case a user has moved or resized the window.

3. After the window's coordinates have been retrieved, call the IDirectDrawSurface3::Blt method to perform a bit block transfer of the movie's video data from the offscreen surface to the primary surface. The loop breaks and the stream state is set to STOP when no renderable video data remains.

       if (g_pSample->Update(0, NULL, NULL, 0) != S_OK) {
       		g_bAppactive = FALSE;
       		g_pMMStream->SetState(STREAMSTATE_STOP);
       	}
       	else {
       	//get window coordinates to blit into
       	GetClientRect(ghWnd, &rect2);
       	point.x = rect2.top;
       	point.y = rect2.left;
       	ClientToScreen(ghWnd, &point);
       	rect2.left = point.x;
       	rect2.top = point.y;
       	point.x = rect2.right;
       	point.y = rect2.bottom;
       	ClientToScreen(ghWnd, &point);
       	rect2.right = point.x;
       	rect2.bottom= point.y;

       //Blit from the offscreen surface to the primary surface
       	hr = g_pPrimarySurface->Blt(&rect2, g_pDDSOffscreen, &rect, DDBLT_WAIT, NULL);

   This function will be called repeatedly from the WinMain function's message pump as long as the g_bAppactive and g_bFileLoaded Boolean values are TRUE.

Release Objects

The ExitCode function releases all objects that the MovieWin application creates, destroys the window, and closes the COM library.

Call this function if the application fails or the user quits the program.

    void ExitCode()
    {
    	//Release MultiMedia streaming Objects
    	if (g_pMMStream != NULL) {
    		g_pMMStream->Release();
    		g_pMMStream= NULL;
    	}
    	if (g_pSample != NULL) {
    		g_pSample->Release();
    		g_pSample = NULL;
    	}
    	if (g_pDDStream != NULL) {
    		g_pDDStream->Release();
    		g_pDDStream= NULL;
    	}
    	if (g_pPrimaryVidStream != NULL) {
    		g_pPrimaryVidStream->Release();
    		g_pPrimaryVidStream= NULL;
    	}
    	//Release DirectDraw Objects
    	if (g_pDDF !=NULL) {
    		g_pDDF->Release();
    		g_pDDF = NULL;
    	}
    	if (g_pPrimarySurface!=NULL) {
    		g_pPrimarySurface->Release();
    		g_pPrimarySurface=NULL;
    	}
    	if (g_pDDSOffscreen !=NULL) {
    		g_pDDSOffscreen->Release();
    		g_pDDSOffscreen= NULL;
    	}
    	if (g_pDDClipper !=NULL) {
    		g_pDDClipper->Release();
    		g_pDDClipper=NULL;
    	}
    	if (g_pDD3 != NULL) {
    		g_pDD3->Release();
    		g_pDD3 = NULL;
    	}
    	if (g_pDD != NULL) {
    		g_pDD->Release();
    		g_pDD = NULL;
    	}

    	PostQuitMessage(0);
    	CoUninitialize();
    }

WndMainProc Function

The WndMainProc callback function handles any messages sent to the window and calls the ExitCode function when the user quits the application. Users generate messages by selecting various items from the menu, including Open, Start, Stop, Pause, About, and Exit.

If the user chooses Open, an IDM_OPEN message is generated and the following code runs.

    //If a file is already open - call STOP first
    					if (g_bAppactive && g_bFileLoaded) {
    						g_pMMStream->SetState(STREAMSTATE_STOP);
    					}

    					bOpen = GetOpenMovieFile(szFilename);
    					if (bOpen) {
    						hr = RenderFileToMMStream(szFilename);
    						hr = InitRenderToSurface();
    						g_bAppactive = g_bFileLoaded = TRUE;
    						g_bPaused = FALSE;		//Take care of any old pauses
    						//Now set the multimedia stream to RUN
    						hr = g_pMMStream->SetState(STREAMSTATE_RUN);
    					}
    					break;

This code first checks whether a file is loaded (g_bFileLoaded) and if it is in a running state (g_bAppactive). If this is the case, the IMultiMediaStream::SetState method is called to stop the stream before another one is loaded through a call to the GetOpenMovieFile function. After the call to GetOpenMovieFile has returned successfully, the RenderFileToMMStream function is called, followed by the InitRenderToSurface function. If both of these functions are successful, the g_bFileLoaded and g_bAppactive Boolean values are set to TRUE and g_bPaused is set to FALSE in case the old file was in a paused state. Finally, the IMultiMediaStream::SetState method is called to set the state to RUN and now the RenderToSurface function will automatically be called through the WinMain function's message pump.

If the user chooses Play from the application's menu, an IDM_START message is generated and the following code runs.

    if (g_bAppactive && g_bFileLoaded)
    					{break;					// If its already playing get out of here
    					}
    					else {
    						if (g_bPaused) {	// If its in a paused state, seek and run
    							g_pMMStream->Seek(StreamTime);
    							g_pMMStream->SetState(STREAMSTATE_RUN);
    							g_bAppactive = TRUE;
    							g_bPaused = FALSE;
    							}
    						else {

    					if (g_bFileLoaded) {	// If a file is actually loaded
    						g_bAppactive = g_bFileLoaded = TRUE;
    						hr = g_pMMStream->SetState(STREAMSTATE_RUN);
    					}
    					else {
    						MessageBox(hWnd, "Please select a movie file first.", "Error", MB_OK);
    					}
    					}
    					}
    					break;

This code first checks if a file is loaded (g_bFileLoaded) and if it is in a running state (g_bAppactive). If this is the case, break is called to ignore the message. If the movie is in a paused state, the IMultiMediaStream::Seek method is called to seek to the correct location in the file, and then the IMultiMediaStream::SetState method is called to set the state to RUN again. The Boolean values g_bAppactive and g_bPaused are reset again to TRUE and FALSE respectively.

If a file is loaded but not in a paused state, it must be in a stopped state. Therefore, if this code succeeds on the if (g_bFileLoaded) call it must restart the movie from the beginning. This involves resetting the g_bAppactive Boolean value to TRUE and calling the IMultiMediaStream::SetState method to set the stream state to RUN.

If the user chooses Pause from MovieWin's menu, an IDM_PAUSE message is generated and the following code runs.

    // Pause if not already in a paused state and you have a file loaded
    					if (!g_bPaused &&g_bFileLoaded) {
    						hr = g_pMMStream->GetTime(&StreamTime);
    						hr = g_pMMStream->SetState(STREAMSTATE_STOP);
    						g_bAppactive = FALSE;
    						g_bPaused	= TRUE;
    					}
    					break;				// If its already paused, just break

In order for the pause key to do anything, the application must not already be in a paused stated (!g_bPaused) and a file must be loaded (g_bFileLoaded). If these two conditions are both TRUE, the IMultiMediaStream::GetTime method is called to store the STREAM_TIME at which the application was paused in the static StreamTime variable, and then the IMultiMediaStream::SetState method set the stream state to STOP. Finally, the g_bAppactive and the g_bPaused global Boolean values must be set to FALSE and TRUE respectively.

If the user chooses Stop from the application's menu, an IDM_STOP message is generated and the following code executes.

    if (g_bFileLoaded) {
    						g_pMMStream->SetState(STREAMSTATE_STOP);
    						StreamTime = 0;	// Reset the stream time to 0
    						g_pMMStream->Seek(StreamTime);	//Run one frame to reset video
    						g_pMMStream->SetState(STREAMSTATE_RUN);
    						RenderToSurface();
    						g_pMMStream->SetState(STREAMSTATE_STOP); // Stop for real this time
    						StreamTime = 0;
    					}
    					g_bAppactive = FALSE;

The preceding code runs if there is a file loaded (g_bFileLoaded). In such a case the IMultiMediaStream::SetState method sets the stream state to STOP and the global STREAM_TIME value is set to zero. Next, the IMultiMediaStream::Seek method and the IMultiMediaStream::SetState method are called to run one frame of the video before the true stop is called. After the RenderToSurface function renders the frame, the IMultiMediaStream::SetState method is called a final time to stop the video. This gives the user the visual experience of seeing the movie rewind to the beginning.

Finally, if the user chooses Exit from the application's menu, an IDM_EXIT message is generated and the following code runs.

    response = MessageBox(hWnd, "Quit the Program?", "Quit", MB_YESNO);
    					if (response==IDYES) SendMessage(ghWnd, WM_DESTROY,0,0);
    					break;

When it runs, this code will prompt the user if he or she really wants to quit the application. If the user chooses Yes, a WM_DESTROY message is sent, which calls the ExitCode function.

Entire MovieWin Example Code

This is the entire code for the MovieWin example code. To compile this code in Microsoft Visual Studio™, create a new Win32 application project and add this code into the project. Follow the directions in the following code comments on how to set your project libraries and include paths.

    // This application uses a Multimedia stream to render
    // a video file to a DirectDrawEx surface contained in
    // a window. It implements a primary DirectDraw surface
    // and an offscreen DirectDraw surface to optimize individual
    // frame blits. It also attaches a DirectDraw clipper to the
    // window to process window overlapping.

    //To compile this program you must have DXMedia SDK 5.1 installed
    //and you will need set your include path under tools/options/directories/include
    //to c:\DXMedia\include and your library path to c:\DXMedia\lib
    //Also link with the following libraries under project/settings/link...
    //amstrmid.lib quartz.lib strmbase.lib ddraw.lib

    #include <windows.h>
    #include <mmstream.h>	// Multimedia stream interfaces
    #include <amstream.h>	// DirectShow multimedia stream interfaces
    #include <ddstream.h>	// DirectDraw multimedia stream interfaces
    #include <initguid.h>   // Defines DEFINE_GUID macro and enables GUID initialization
    #include <ddrawex.h>	// DirectDrawEx interfaces
    #include "resource.h"	// Resources for the menu bar

    #define APPLICATIONNAME "Multimedia Stream In Window"
    #define CLASSNAME "MMSDDRAWEXWINDOW"

    //Global variables
    HWND			  ghWnd;
    HINSTANCE		ghInst;
    BOOL			  g_bAppactive=FALSE,		// The window is active
    				    g_bFileLoaded = FALSE,  // There is a file loaded
    				    g_bPaused=FALSE;		// The movie has been paused
    RECT			  rect, rect2;			// Rectangles for screen coordinates

    //DirectDrawEx Global interfaces
    IDirectDraw			*g_pDD=NULL;
    IDirectDraw3		*g_pDD3=NULL;
    IDirectDrawFactory	*g_pDDF=NULL;
    IDirectDrawSurface	*g_pPrimarySurface=NULL,
    					*g_pDDSOffscreen=NULL;
    IDirectDrawClipper	*g_pDDClipper=NULL;

    //Global MultiMedia streaming interfaces
    IMultiMediaStream		*g_pMMStream=NULL;
    IMediaStream			*g_pPrimaryVidStream=NULL;
    IDirectDrawMediaStream	*g_pDDStream=NULL;
    IDirectDrawStreamSample *g_pSample=NULL;

    //Function prototypes
    int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow);
    LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    HRESULT InitDDrawEx();
    BOOL GetOpenMovieFile(LPSTR szName);
    HRESULT RenderFileToMMStream(LPCTSTR szFilename);
    HRESULT InitRenderToSurface();
    void RenderToSurface();
    void ExitCode();

    void ExitCode()
    {
    	//Release MultiMedia streaming Objects
    	if (g_pMMStream != NULL) {
    		g_pMMStream->Release();
    		g_pMMStream= NULL;
    	}
    	if (g_pSample != NULL) {
    		g_pSample->Release();
    		g_pSample = NULL;
    	}
    	if (g_pDDStream != NULL) {
    		g_pDDStream->Release();
    		g_pDDStream= NULL;
    	}
    	if (g_pPrimaryVidStream != NULL) {
    		g_pPrimaryVidStream->Release();
    		g_pPrimaryVidStream= NULL;
    	}
    	//Release DirectDraw Objects
    	if (g_pDDF !=NULL) {
    		g_pDDF->Release();
    		g_pDDF = NULL;
    	}
    	if (g_pPrimarySurface!=NULL) {
    		g_pPrimarySurface->Release();
    		g_pPrimarySurface=NULL;
    	}
    	if (g_pDDSOffscreen !=NULL) {
    		g_pDDSOffscreen->Release();
    		g_pDDSOffscreen= NULL;
    	}
    	if (g_pDDClipper !=NULL) {
    		g_pDDClipper->Release();
    		g_pDDClipper=NULL;
    	}
    	if (g_pDD3 != NULL) {
    		g_pDD3->Release();
    		g_pDD3 = NULL;
    	}
    	if (g_pDD != NULL) {
    		g_pDD->Release();
    		g_pDD = NULL;
    	}

    	PostQuitMessage(0);
    	CoUninitialize();
    }

    //Create the stream sample which will be used to call updates on the video
    HRESULT InitRenderToSurface()
    {
    	HRESULT			hr;
    	DDSURFACEDESC	ddsd;

    	//Use the multimedia stream to get the primary video media stream
        hr = g_pMMStream->GetMediaStream(MSPID_PrimaryVideo, &g_pPrimaryVidStream);
    	if (FAILED(hr))
    	{   goto Exit;
    	}
    	//Use the media stream to get the IDirectDrawMediaStream
        hr = g_pPrimaryVidStream->QueryInterface(IID_IDirectDrawMediaStream, (void **)&g_pDDStream);
    	if (FAILED(hr))
    	{   goto Exit;
    	}
    	//Must set dwSize before calling GetFormat
        ddsd.dwSize = sizeof(ddsd);
        hr = g_pDDStream->GetFormat(&ddsd, NULL, NULL, NULL);
    	if (FAILED(hr))
    	{   goto Exit;
    	}

    	  rect.top = rect.left = 0;
        rect.bottom = ddsd.dwHeight;
        rect.right = ddsd.dwWidth;

    	//Create the stream sample
    	hr = g_pDDStream->CreateSample(g_pDDSOffscreen, &rect, 0, &g_pSample);
    	if (FAILED(hr))
    	{   goto Exit;
    	}
    Exit:
    	if (FAILED(hr))
    	{	MessageBox(ghWnd, "Initialization failure in InitRenderToSurface", "Error", MB_OK);
    		return E_FAIL;
    	}
    	return NOERROR;
    }

    //Perform frame by frame updates and blits. Set the stream
    //state to STOP if there are no more frames to update.
    void RenderToSurface()
    {
    	HRESULT		hr;
    	POINT		point;

    	//update each frame
    	if (g_pSample->Update(0, NULL, NULL, 0) != S_OK) {
    		g_bAppactive = FALSE;
    		g_pMMStream->SetState(STREAMSTATE_STOP);
    	}
    	else {
    	//get window coordinates to blit into
    	GetClientRect(ghWnd, &rect2);
    	point.x = rect2.top;
    	point.y = rect2.left;
    	ClientToScreen(ghWnd, &point);
    	rect2.left = point.x;
    	rect2.top = point.y;
    	point.x = rect2.right;
    	point.y = rect2.bottom;
    	ClientToScreen(ghWnd, &point);
    	rect2.right = point.x;
    	rect2.bottom= point.y;

    	//blit from the offscreen surface to the primary surface
    	hr = g_pPrimarySurface->Blt(&rect2, g_pDDSOffscreen, &rect, DDBLT_WAIT, NULL);
    	if(FAILED(hr))
        {   MessageBox(ghWnd, "Blt failed", "Error", MB_OK);
    		    ExitCode();
        }
    	}
    }

    //Renders a file to a multimedia stream
    HRESULT RenderFileToMMStream(LPCTSTR szFilename)		//IMultiMediaStream **ppMMStream
    {
    	HRESULT hr;
    	IAMMultiMediaStream *pAMStream=NULL;

    //Convert filename to Unicode
    	WCHAR wFile[MAX_PATH];
    	MultiByteToWideChar(CP_ACP, 0, szFilename, -1, wFile,
    								sizeof(wFile)/sizeof(wFile[0]));

    	//Create the AMMultiMediaStream object
        hr =CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER,
                                          IID_IAMMultiMediaStream, (void **)&pAMStream);
    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "Could not create a CLSID_MultiMediaStream object\n"
    		"Check you have run regsvr32 amstream.dll\n", "Error", MB_OK);
    		  return E_FAIL;
    	}

    	//Initialize stream
        hr = pAMStream->Initialize(STREAMTYPE_READ, 0, NULL);
    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "Initialize failed.", "Error", MB_OK);
    		  return E_FAIL;
    	}
    	//Add primary video stream
        hr = pAMStream->AddMediaStream(g_pDD3, &MSPID_PrimaryVideo, 0, NULL);
    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "AddMediaStream failed.", "Error", MB_OK);
    		  return E_FAIL;
    	}
    	//Add primary audio stream
        hr = pAMStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, AMMSF_ADDDEFAULTRENDERER, NULL);
    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "AddMediaStream failed.", "Error", MB_OK);
    		  return E_FAIL;
    	}
    	//Opens and automatically creates a filter graph for the specified media file
    	hr = pAMStream->OpenFile(wFile, 0);
    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "File format not supported.", "Error", MB_OK);
    		  return E_FAIL;
    	}

    	//save the local stream to the global variable
    	g_pMMStream = pAMStream;
    	// Add a reference to the file
    	pAMStream->AddRef();

    	return NOERROR;
    }

    HRESULT InitDDrawEx()
    {
    	HRESULT			hr=NOERROR;
    	DDSURFACEDESC	ddsd, ddsd2;

    	CoInitialize(NULL);

    	//Create a DirectDrawFactory object
    	hr = CoCreateInstance(CLSID_DirectDrawFactory, NULL, CLSCTX_INPROC_SERVER,
    							IID_IDirectDrawFactory, (void **)&g_pDDF);
    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "Couldn't create DirectDrawFactory", "Error", MB_OK);
    		  return E_FAIL;
    	}

    	//Call the IDirectDrawFactory::CreateDirectDraw method to create the
    	//DirectDraw object, set the cooperative level, and get the address
    	//of an IDirectDraw interface pointer
    	hr = (g_pDDF->CreateDirectDraw(NULL, GetDesktopWindow(), DDSCL_NORMAL,
    				NULL, NULL, &g_pDD));

    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "Couldn't create DirectDraw object", "Error", MB_OK);
    		  return E_FAIL;
    	}

    	//Now query for the new IDirectDraw3 interface
    	hr =(g_pDD->QueryInterface(IID_IDirectDraw3, (LPVOID*)&g_pDD3));

    	if (FAILED(hr))
    	{   MessageBox(ghWnd, "Couldn't get IDirectDraw3", "Error", MB_OK);
    		  return E_FAIL;
    	}

        //Initialize the DDSURFACEDESC structure for the primary surface
    	  ZeroMemory(&ddsd, sizeof(ddsd));
        ddsd.dwSize = sizeof(ddsd);
    	  ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
        hr = g_pDD3->CreateSurface(&ddsd, &g_pPrimarySurface, NULL);

        if(FAILED(hr))
        {   MessageBox(ghWnd, "Couldn't create Primary Surface", "Error", MB_OK);
        	  return E_FAIL;
    	}

    	// Now, do the same for the offscreen surface.

        // The offscreen surface needs to use the same pixel format as the primary.
        // Query the primary surface to for its pixel format.
        hr = g_pPrimarySurface->GetSurfaceDesc(&ddsd);
        if(FAILED(hr))
        {   MessageBox(ghWnd, "Couldn't GetSurfaceDesc", "Error", MB_OK);
    		    return E_FAIL;
        }

    	// Now, set the info for the offscreen surface, using the primary's pixel format.
        ZeroMemory(&ddsd2, sizeof(ddsd2));
    	ddsd2.dwSize = sizeof(ddsd2);
    	ddsd2.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
        ddsd2.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
        ddsd2.dwHeight = ddsd.dwHeight;	//set the height of the surfaces equal
        ddsd2.dwWidth  = ddsd.dwWidth;	//set the width of the surfaces equal
        ddsd2.ddpfPixelFormat = ddsd.ddpfPixelFormat; //set the pixel formats equal

        // Now, create the offscreen surface and query for the latest interface.
    	hr = g_pDD3->CreateSurface(&ddsd2, &g_pDDSOffscreen, NULL);
    	if(FAILED(hr))
        {   MessageBox(ghWnd, "Couldn't create Offscreen Surface", "Error", MB_OK);
    		    return E_FAIL;
        }

    	//Add code for Clipper
    	hr = g_pDD3->CreateClipper(0, &g_pDDClipper, NULL);
    	if(FAILED(hr))
        {   MessageBox(ghWnd, "Couldn't create Clipper", "Error", MB_OK);
    		    return E_FAIL;
        }

    	hr = g_pPrimarySurface->SetClipper(g_pDDClipper);
    	if(FAILED(hr))
        {   MessageBox(ghWnd, "Call to SetClipper failed", "Error", MB_OK);
    		    return E_FAIL;
        }

    	hr = g_pDDClipper->SetHWnd(0, ghWnd);
    	if(FAILED(hr))
        {   MessageBox(ghWnd, "Call to SetHWnd failed", "Error", MB_OK);
    	    	return E_FAIL;
        }

    	return NOERROR;
    }

    // Display the open dialog box to retrieve the user-selected movie file
    BOOL GetOpenMovieFile(LPSTR szName)//LPSTR szName
    {
    	OPENFILENAME	ofn;

    	ofn.lStructSize       = sizeof(OPENFILENAME);
    	ofn.hwndOwner         = ghWnd;
    	ofn.lpstrFilter       = NULL;
    	ofn.lpstrFilter       = "Video (*.avi;*.mpg;*.mpeg)\0*.avi;*.mpg;*.mpeg\0All Files (*.*)\0*.*\0";
    	ofn.lpstrCustomFilter = NULL;
    	ofn.nFilterIndex      = 1;
    	*szName = 0;
    	ofn.lpstrFile         = szName;
    	ofn.nMaxFile          = MAX_PATH;
    	ofn.lpstrInitialDir   = NULL;
    	ofn.lpstrTitle        = NULL;
    	ofn.lpstrFileTitle    = NULL;
    	ofn.lpstrDefExt       = NULL;
    	ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;
    	return GetOpenFileName((LPOPENFILENAME)&ofn);
    }

    LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

       {  // WndMainProc //

    	int						response;
    	HRESULT					hr;
    	BOOL					bOpen;
    	static TCHAR			szFilename[MAX_PATH];
    	static STREAM_TIME		StreamTime;		// Stream time of the movie file

    	switch(message)
    	{
    		case WM_COMMAND:
    			{
                   switch(wParam)
    			   //Program menu option
    			   {
    				case IDM_OPEN:
    					//If a file is already open - call STOP first
    					if (g_bAppactive && g_bFileLoaded) {
    						g_pMMStream->SetState(STREAMSTATE_STOP);
    					}

    					bOpen = GetOpenMovieFile(szFilename);
    					if (bOpen) {
    						hr = RenderFileToMMStream(szFilename);
    						if (FAILED(hr)) {
    							ExitCode();
    							break;
    							}
    						hr = InitRenderToSurface();
    						if (FAILED(hr)) {
    							ExitCode();
    							break;
    							}
    						g_bAppactive = g_bFileLoaded = TRUE;
    						g_bPaused = FALSE;		//Take care of any old pauses
    						//Now set the multimedia stream to RUN
    						hr = g_pMMStream->SetState(STREAMSTATE_RUN);
    						if (FAILED(hr))
    						{   ExitCode();
    						}
    					}
    					break;

    				case IDM_START:
    					if (g_bAppactive && g_bFileLoaded)
    					{break;					// If its already playing get out of here
    					}
    					else {
    						if (g_bPaused) {	// If its in a paused state, seek and run
    							g_pMMStream->Seek(StreamTime);
    							g_pMMStream->SetState(STREAMSTATE_RUN);
    							g_bAppactive = TRUE;
    							g_bPaused = FALSE;
    							}
    						else {

    					if (g_bFileLoaded) {	// If a file is actually loaded
    						hr = RenderFileToMMStream(szFilename);	// Render file to stream
    						if (FAILED(hr)) {
    							ExitCode();
    							break;
    							}
    						hr = InitRenderToSurface();		// Render stream
    						if (FAILED(hr)) {
    							ExitCode();
    							break;
    							}
    						g_bAppactive = g_bFileLoaded = TRUE;
    						//Now set the multimedia stream to RUN
    						hr = g_pMMStream->SetState(STREAMSTATE_RUN);
    						if (FAILED(hr))
    						{   ExitCode();
    						}
    					}
    					else {
    						MessageBox(hWnd, "Please select a movie file first.", "Error", MB_OK);
    					}
    					}
    					}
    					break;

    				case IDM_PAUSE:
    					// Pause if not already in a paused state and you have a file loaded
    					if (!g_bPaused &&g_bFileLoaded) {
    						hr = g_pMMStream->GetTime(&StreamTime);
    						hr = g_pMMStream->SetState(STREAMSTATE_STOP);
    						g_bAppactive = FALSE;
    						g_bPaused	= TRUE;
    					}
    					break;				// If its already paused, just break

    				case IDM_STOP:
    					if (g_bFileLoaded) {
    						g_pMMStream->SetState(STREAMSTATE_STOP);
    						StreamTime = 0;	// Reset the stream time to 0
    						g_pMMStream->Seek(StreamTime);	//Run one frame to reset video
    						g_pMMStream->SetState(STREAMSTATE_RUN);
    						RenderToSurface();
    						g_pMMStream->SetState(STREAMSTATE_STOP); // Stop for real this time
    						StreamTime = 0;
    					}
    					g_bAppactive = FALSE;
    					break;

    				case IDM_ABOUT:
    					MessageBox(hWnd, "This application uses multimedia streaming to"
    						" render a video file to a DirectDraw surface created through DirectDrawEx.",
    						"About", MB_OK);
    					break;

    				case IDM_EXIT:
    					response = MessageBox(hWnd, "Quit the Program?", "Quit", MB_YESNO);
    					if (response==IDYES) SendMessage(ghWnd, WM_DESTROY,0,0);
    					break;
                }
    		   break;
    			}
    		break;

            case WM_DESTROY:
    		   ExitCode();
               break;

    		case WM_ACTIVATE:
    			if((BOOL)LOWORD(wParam) == WA_INACTIVE)
    			{
    				//App is not active
    				g_bAppactive = FALSE;
    			}
    			else
    			{
    				//Set app to active if a file is loaded
    				g_bAppactive = (g_bFileLoaded)?TRUE:FALSE;
    			}
    			break;

            default:
               return DefWindowProc(hWnd, message, wParam, lParam);

          }  // Window msgs handling

          return FALSE;

       }  // WndMainProc //

    int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)

       {  // WinMain //

          MSG		msg;
          WNDCLASS  wc;
    	    HRESULT	hr;

          ZeroMemory(&wc, sizeof wc);
          wc.lpfnWndProc = WndMainProc;
          ghInst = wc.hInstance = hInstC;
    	    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
          wc.lpszClassName = CLASSNAME;
          wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
          wc.hCursor = LoadCursor(NULL, IDC_ARROW);
          RegisterClass(&wc);

          ghWnd = CreateWindowEx(WS_EX_WINDOWEDGE,
    		CLASSNAME,
            APPLICATIONNAME,
            WS_VISIBLE |WS_POPUP |WS_OVERLAPPEDWINDOW,
            150,
            150,
            280,
            250,
            0,
            0,
            ghInst,
            0);
    	  if (ghWnd) {				// If the call to create window succeeds,
    		  hr = InitDDrawEx();	// initialize DirectDrawEx
    		  if (FAILED(hr)) {
    			  ExitCode();
    		  }
    	  }
    	  else {
    		  MessageBox(ghWnd, "Couldn't create window.", "Error", MB_OK);
    		  return 0;
    	  }

    	  ShowWindow(ghWnd, SW_NORMAL);
          UpdateWindow(ghWnd);

    	  while(1){
    		//The PeekMessage function checks a thread message queue
    		//for a message and places the message (if any) in the specified structure.
    		if(PeekMessage(&msg, NULL, 0,0,PM_NOREMOVE)){

    			// Quit if WM_QUIT found
    			if(!GetMessage(&msg,NULL, 0, 0)) return (msg.wParam);

    			// Otherwise handle the messages
    				TranslateMessage(&msg);		// Allow input
    				DispatchMessage(&msg);		// Send to appropriate process.
    		}
    		else{
    			// If there are no other windows messages...
    			// Render frame by frame (but only if the App is the active
    			// window and a file is actually loaded)
    			if (g_bFileLoaded && g_bAppactive) {
    				RenderToSurface();
    				}
    			}
    		}
    	  return msg.wParam;

    }  // WinMain //

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