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® 9x application that is an extension of the ShowStrm Sample (Multimedia Streaming Application). MovieWin uses multimedia streaming to render a video file to a DirectDraw surface created through DirectDrawEx. It implements a primary DirectDraw surface and an off-screen DirectDraw surface to optimize frame blitting. It also attaches a DirectDraw clipper to the window to process window overlapping.

This article contains the following sections.

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.

The example also 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 in the MovieWin example code.

To compile the MovieWin example code you must have the Microsoft® 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 following libraries, under Project/Settings/Link: Amstrmid.lib, Quartz.lib, Strmbase.lib, and Ddraw.lib (DirectDrawEx does not provide its own library).

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 variables g_bAppactive, g_bFileLoaded, and g_bPaused 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 across function calls within the application. 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, Multimedia Streaming, and the 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

First, you need to create a WinMain function. WinMain is a generic Windows function. You need to revise this function if 1) your application needs to know when another instance of the application is running, or 2) you need to access the command line. Otherwise, you can probably use the code in this standard function without change. For an example of a generic WinMain function, see the MovieWin Example Code.

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

The message pump (a loop that gets and dispatches messages) is a standard Windows message pump containing the TranslateMessage and DispatchMessage functions. A message pump cycles through a message loop, checking the message queue for available messages. If a message is available, the message pump dispatches it for action. Before the code reaches the TranslateMessage and DispatchMessage 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. However, if there are no messages in the message queue, the process will test 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 the GetOpenMovieFile and RenderFileToMMStream functions), g_bFileLoaded and g_bAppactive are both 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 renders frame by frame until completion or until the PeekMessage function interrupts with an outside message to the window. If the movie is paused or 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 application 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 off-screen 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 the IDirectDrawSurface interface.
    g_pDD3->CreateSurface(&ddsd, &g_pPrimarySurface, NULL);
  6. Create the off-screen surface where the IStreamSample::Update method will send the individual movie frames before they are blitted onto the screen. Using an off-screen surface optimizes video performance 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 off-screen surface with the height, width, and pixel format identical 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 off-screen 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 off-screen surface.
    g_pDD3->CreateSurface(&ddsd2, &g_pDDSoffscreen, NULL)

    At this point, you will have two identical DirectDraw surfaces: the off-screen surface used to update the movie frames, and the primary surface, which your user will see. The primary surface will contain the video after the data is blitted from the off-screen 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 (informally 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 will 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, 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 Microsoft® DirectShow® functionality to the application. It first retrieves the address of a pointer to the IAMMultiMediaStream interface, then uses the pointer 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 Multimedia Streaming 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 off-screen 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. One detail that is easy to overlook on this call is that you must first 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 off-screen 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 off-screen 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 off-screen surface to the primary surface. When the movie is complete, it sets 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 discards the previous video image and grabs the next image from the stream.

    If the update is successful, the Win32 GetClientRect and 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 off-screen surface to the primary surface. When no renderable video data remains, the loop breaks and the stream state is set to STOP.
    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 off-screen 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 choosing 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 in a running state (g_bAppactive). If so, it calls the IMultiMediaStream::SetState method to stop the stream before loading another one 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. The RenderToSurface function will now automatically be called through the WinMain function's message pump.

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

if (g_bAppactive && g_bFileLoaded)    
                    {break;                  // if it's already playing get out of here
                    }
                    else {
                        if (g_bPaused) {     // if it's 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 whether a file is loaded (g_bFileLoaded) and in a running state (g_bAppactive). If so, it calls break to ignore the message. Otherwise, it makes further tests:

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 or a running state, it can only be in a stopped state, and the code 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 the application 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 it's already paused, just break

In order for the pause key to do anything, the application must not already be in a paused state (!g_bPaused) and a file must be loaded (g_bFileLoaded). If these two conditions are both TRUE, the IMultiMediaStream::GetTime method stores the STREAM_TIME at which the application was paused in the static StreamTime variable, and then the IMultiMediaStream::SetState method sets the stream state to STOP. Finally, the g_bAppactive and the g_bPaused global Boolean values are updated 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;

If a file is loaded (g_bFileLoaded), 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 prompts 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 off-screen 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 DirectX Media 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 off-screen 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 off-screen surface.

    // The off-screen 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 off-screen 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 off-screen surface and query for the latest interface.
    hr = g_pDD3->CreateSurface(&ddsd2, &g_pDDSoffscreen, NULL);
    if(FAILED(hr))
    {   MessageBox(ghWnd, "Couldn't create off-screen 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 it's already playing get out of here
                    }
                    else {
                        if (g_bPaused) {    // if it's 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 it's 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 application to active if a file is loaded.
                g_bAppactive = (g_bFileLoaded)?TRUE:FALSE;    
            }
            break;
    
        default:
           return DefWindowProc(hWnd, message, wParam, lParam);
    
      }  // window messages 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 application is the active
            // window and a file is actually loaded).
            if (g_bFileLoaded && g_bAppactive) {    
                RenderToSurface();    
                }
            }
        }
      return msg.wParam;

      

}  // WinMain //

Top of Page Top of Page
© 2000 Microsoft and/or its suppliers. All rights reserved. Terms of Use.