Use Multimedia Streaming in DirectShow Applications


This section describes and demonstrates how to support multimedia streaming in Microsoft® DirectShow™ applications. DirectShow applications typically use multimedia streaming to send audio and video data directly to a Microsoft DirectDraw® surface for rendering, instead of attaching playback to a specific window. This section has short conceptual explanations of windowless playback and multimedia streams, as well as additional detail on the multimedia streaming architecture and a minimal code demonstration of using streams to perform windowless playback of DirectShow-supported media files.

This section contains the following topics.

Programmers who want to use multimedia streaming in their applications should be familiar with COM programming concepts, DirectDraw and its associated objects, and DirectShow media playback. For information on DirectDraw, consult the Microsoft DirectX SDK documentation. The DirectShow SDK documentation includes many examples of media file playback using C/C++; see About DirectShow and the included samples for more information. If you need information on programming with COM and OLE, consult reference materials such as Inside OLE by Kraig Brockschmidt or Understanding ActiveX and OLE by David Chappell.

Windowless Playback

Typically, applications display their video output in a clearly bounded rectangle, — the window. Each window has certain properties in common with other windows, such as menus, close buttons, and so forth. This shared behavior is helpful because it provides a measure of consistency and reliability to programming procedures and the user interface. DirectShow typically uses windows for media playback, because of the low programming overhead and consistent interface. However, there are a number of situations where an application developer wants to divorce media playback from the window and gain complete control over its appearance. For example, if you were creating a three-dimensional computer model of a museum tour, complete with moving exhibits and an animated tour guide, it would not be appropriate (or lifelike) to show each element of the tour in a separate window; you would need to integrate all of the elements together into a single presentation. By attaching the media playback to a DirectDraw surface instead of a window, you gain complete control over its appearance and behavior.

DirectDraw surfaces represent a portion of a system's video memory. Once you designate a surface as the destination of a movie's video data, you can blit the data to the surface in the same way you would normally blit color and texture information. Because it is a normal DirectDraw surface, you can manipulate it in any manner supported by the DirectDraw interfaces; you can play it back as the background of a game, texture map it into a three-dimensional environment, and so forth. While this level of control adds some programming overhead to your application, these effects would be impossible to do in a normal window.

Multimedia Streams

Audio and video data is, at its most basic, a sequence of information that specifies characteristics like color, resolution, frequency, and volume. Because there are a large number of devices and data formats related to media, moving data from its origin to its destination is a very convoluted process; you must know exactly how the original device formats its information, what characteristics the display format has, and how to convert the device information from its original format to a format suitable for rendering or storage. Because the exact steps in this process are different for every device, it is often difficult to handle multiple devices (such as a video camera, movie data file, and Internet URL) in a single application. Applications can, however, avoid much of this difficulty by using multimedia streaming as the data source. The streaming architecture automatically handles the process of data conversion and formatting, producing a consistently formatted data source ready for rendering or file storage. Thus, applications only need to handle the presentation of the data and not the data conversion.

Code Walk-through of a Simple Application

Using multimedia streams in a DirectShow application is fairly straightforward; the following steps describe the process.

  1. Open a media file that DirectShow supports.
  2. Create a multimedia stream for each of the file's media types; typically, this will be one video and one audio stream.
  3. Create a DirectDraw surface and associate it with the video stream.
  4. Render the stream data, which will then play back on the surface.

The following code sample, which you can find in its entirety in the \Streams\Simple\Main.cpp file included with the DirectShow SDK, demonstrates these steps. The complete file comprises three functions: OpenMMStream, RenderStreamToSurface, and main. OpenMMStream creates the audio and video multimedia streams from the media file, RenderStreamToSurface does the actual surface rendering, and main calls the other two functions appropriately. Because this example is a command-line application, you must supply the name of the media file as a parameter when you run the program. In Main.cpp, the following macro handles error checking.


#define CHECK_ERROR(x)     \
   if (FAILED(hr = (x))) { \
       printf(#x "  failed with HRESULT(0x%8.8X)\n", hr); \
       goto Exit;          \
   }

Each application that uses multimedia streaming must include the correct header files. The following list contains the stream-related header files from Main.cpp; the DirectShow SDK includes all of these header files.


#include "ddraw.h"	// DirectDraw interfaces
#include "mmstream.h"	// Multimedia stream interfaces
#include "amstream.h"	// DirectShow multimedia stream interfaces
#include "ddstream.h"	// DirectDraw multimedia stream interfaces

The code in Main.cpp is intended to be the minimum amount of programming necessary to implement multimedia streams, so it is appropriate to read it as a series of required steps. The following instructions illustrate all of the important concepts from Main.cpp, but don't necessarily include every line of code. For the complete code, refer to Main.cpp.

Creating a Multimedia Stream Linked to a DirectShow File

To create a multimedia stream and link it to a media file, perform the following steps. You do not necessarily need to perform the steps in the given order.

1) The OpenMMStream function creates a multimedia stream and attaches the stream to a valid input media file. The pszFileName parameter specifies the name of the media file, whose type DirectShow must support. The pDD parameter specifies an IDirectDraw interface that points to the destination DirectDraw object. When this function creates the multimedia stream, it attaches the stream's video portion to the object by using this pointer. The ppMMStream parameter represents a global stream pointer. Once this function creates a valid local stream, it points this parameter to the stream so other functions can use the stream as needed.


HRESULT OpenMMStream(const char * pszFileName, IDirectDraw *pDD,
			   IMultiMediaStream **ppMMStream) {

2) Declare a local IAMMultiMediaStream pointer, create a stream object, and initialize it. You should use the local pAMStream pointer during the stream's creation; don't use the global ppMMStream pointer until you are sure the stream and its media file are valid.


    *ppMMStream = NULL;	// Initialize global stream pointer to null
    IAMMultiMediaStream *pAMStream;
    HRESULT hr;		// Function's return value

    CHECK_ERROR(CoCreateInstance(CLSID_AMMultiMediaStream, NULL,
					   CLSCTX_INPROC_SERVER, IID_IAMMultiMediaStream,
					   (void **)&pAMStream));
    CHECK_ERROR(pAMStream->Initialize(STREAMTYPE_READ,
					   AMMSF_NOGRAPHTHREAD, 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 you 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.


    CHECK_ERROR(pAMStream->AddMediaStream(pDD, MSPID_PrimaryVideo, 0, NULL));
    CHECK_ERROR(pAMStream->AddMediaStream(NULL, MSPID_PrimaryAudio,
							AMMSF_ADDDEFAULTRENDERER, NULL));

4) Convert the provided file name to a wide (Unicode) string and open the file. If the file name specifies a valid media file, DirectShow attaches the audio and video tracks to the streams you created earlier in the function. Point the ppMMStream parameter to the stream and increment the pointer's reference count.


    WCHAR	wPath[MAX_PATH];		// Wide (32-bit) string name
    MultiByteToWideChar(CP_ACP, 0, pszFileName, -1, wPath,
				sizeof(wPath)/sizeof(wPath[0]));

    CHECK_ERROR(pAMStream->OpenFile(wPath, 0));
    *ppMMStream = pAMStream;		// Set global pointer to local pointer
    pAMStream->AddRef();		// Add a reference to the file

Now that you have valid streams and a pointer to them, this function is complete.

Render the Video Data to a DirectDraw Surface

To render the video portion of a multimedia stream to a DirectDraw surface, perform the following steps. You do not necessarily need to perform the steps in the given order.

1) The RenderStreamToSurface function handles the actual rendering; it creates and initializes the required DirectDraw surface, and blits the video stream's data to the surface. The pDD parameter points to a global DirectDraw object, which you later use to create the surface. The pPrimary parameter is the primary rendering surface; it sends all blitted video data from the video stream, which the pMMStream parameter points to.


HRESULT RenderStreamToSurface(IDirectDraw *pDD, IDirectDrawSurface *pPrimary,
                              IMultiMediaStream *pMMStream) {

2) Create local variables for the surface, media streams, and video sample. When you blit data to the DirectDraw surface, you will use these local variables to store the individual frame and video sample information.


    IMediaStream *pPrimaryVidStream = NULL;
    IDirectDrawMediaStream *pDDStream = NULL;
    IDirectDrawSurface *pSurface = NULL;
    IDirectDrawStreamSample *pSample = NULL;

3) Retrieve the video stream from the global stream, which the pMMStream pointer specifies; the IMultiMediaStream::GetMediaStream method associates the local IMediaStream pointer with the retrieved stream. You can then use that pointer to obtain a DirectDraw media stream pointer, which you will need to retrieve the video format.


    CHECK_ERROR(pMMStream->GetMediaStream(MSPID_PrimaryVideo,
		    &pPrimaryVidStream));
    CHECK_ERROR(pPrimaryVidStream->QueryInterface(
		    IID_IDirectDrawMediaStream, (void **)&pDDStream));

4) Create a DirectDraw surface and a bounding rectangle to use for playback. Call IDirectDrawMediaStream::GetFormat to retrieve the video format and set the dimensions of the rectangle to match the format dimensions.


    DDSURFACEDESC ddsd;			// Surface characteristics
    ddsd.dwSize = sizeof(ddsd);

    CHECK_ERROR(pDDStream->GetFormat(&ddsd, NULL, NULL));
    RECT rect;				// Playback rectangle
    rect.top = rect.left = 0;
    rect.bottom = ddsd.dwHeight;
    rect.right = ddsd.dwWidth;

    CHECK_ERROR(pDD->CreateSurface(&ddsd, &pSurface, NULL));

5) Create the first video sample and attach it to the desired playback surface. You can then blit all samples from the video stream directly to the surface by calling the DIrectDraw Surface's Update method in a loop. Each loop iteration throws out the previous video image and grabs the next image from the stream. The loop breaks once there is no remaining renderable video data.


    CHECK_ERROR(pDDStream->CreateSample(pSurface, NULL, 0, &pSample));

    while (true) {
        if (pSample->Update(0, NULL, NULL, 0) != S_OK) {
            break;
        }
        pPrimary->Blt(&rect, pSurface, &rect, DDBLT_WAIT, NULL);
    }

6) Release all local pointers.


    RELEASE(pPrimaryVidStream);
    RELEASE(pDDStream);
    RELEASE(pSample);
    RELEASE(pSurface);

    return hr;
}

Once DirectShow finishes rendering all available data, the function is complete.

Run the Program

To obtain a valid media filename and run the program, perform the following steps. You do not necessarily need to perform the steps in the given order.

1) Create a main function to obtain the file name and run the rendering process. The following example takes the media file name as a command-line parameter.


int main(int argc, char *argv[]) {

2) Create a global DirectDraw object; once you have a valid object, create a surface that you will later use for video playback. This example calls the Win32 GetDesktopWindow function to associate the surface with the desktop, reducing the amount of required configuration code.


    CoInitialize(NULL);		// Initialize the COM objects

    // Create the DirectDraw object and its interface pointer
    IDirectDraw *pDD;
    HRESULT hr = DirectDrawCreate(NULL, &pDD, NULL);

    if (SUCCEEDED(hr)) {	// The object is valid
    	DDSURFACEDESC ddsd;	// Surface characteristics
    	IDirectDrawSurface *pPrimarySurface;

    	pDD->SetCooperativeLevel(GetDesktopWindow(), DDSCL_NORMAL);
    	ddsd.dwSize = sizeof(ddsd);
    	ddsd.dwFlags = DDSD_CAPS;
    	ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    	hr = pDD->CreateSurface(&ddsd, &pPrimarySurface, NULL);

3) Create the multimedia stream and call the previously defined functions. Once the functions finish execution, make sure to release all pointers at the correct times. Once playback is complete, call the Win32 CoUninitialize function and return. Once DirectShow finishes playback of the file, it returns control to the command line.


    	if (SUCCEEDED(hr)) {
            IMultiMediaStream *pMMStream;
            hr = OpenMMStream(argv[1], pDD, &pMMStream);
            if (SUCCEEDED(hr)) {
                RenderStreamToSurface(pDD, pPrimarySurface, pMMStream);
                pMMStream->Release();
            }
    	    pPrimarySurface->Release();
    }
    CoUninitialize();	// Release COM objects
    return 0;		// Success
}

Now that you know how to direct streamed video data to a DirectDraw surface, you can use this functionality any way you would normally use DirectDraw surfaces. A typical use would be to texture map the playback surface onto a Direct3D primitive object and incorporate it as part of a three-dimensional environment. For information on controlling any part of DirectDraw, consult the DirectX SDK documentation.

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