CMPT365 Multimedia PA2 Tutorial Cheng Lu
DirectShow For Media Playback In Windows
(Note: complete source is available at
http://www.cs.sfu.ca/CC/365/mark/material/work/dsmediaplayer.zip)
A few years ago, Microsoft introduced a media-streaming layer on top of DirectX, called
DirectShow, that was meant to handle virtually any type of media.
This tutorial walks you through the creation of a DirectShow application supporting the
windows GUI. The application allows you to select media files for playback using the
standard windows file open box. It allows you to start and stop media playback using
windows menu commands.
Step1:
Create a new project named DSMediaPlayer.
Select Win32 Application as your project type.
Click “OK” to continue.
In the next dialog select “A typical ‘Hello World!’ application”.
Step 2: Create menu options
Select the menu tab in the workspace window.
Add the menu items Open, Play and Stop as shown in the following figure.
Specify the ID’s of these items as follows
Menu Caption Menu ID
Open IDM_OPEN
Play IDM_PLAY
Stop IDM_STOP
Step3:
Open the project settings dialog.
Select the link tab and select the General Category. Enter the link library strmbasd.lib at
the end in the Object/library modules edit box.
Step3:
Modify the code as follows. For sake of brevity only relevant portions of code are
reproduced below.
Include the following header files
#include <commdlg.h>
#include <dshow.h>
// Global Variables:
//Declare global variables
HWND hWnd;
IGraphBuilder *pGraph;
IMediaControl *pMediaControl;
IMediaEventEx *pEvent;
IVideoWindow *pVidWindow;
//Buffer for storing media filename
char szMediaFileName[256]="";
wchar_t szWMediaFileName[256];
//Media selection filter
char szFilterString[] = "All media files (*.avi, *.mpg, *.wav,
*.mp3)\0*.avi;*.mpg;*.wav;*.mp3;*.mid\0";
Make the following additions to the WinMain function
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
CoInitialize(NULL);
//Create the DirectShow FilterGraph component and obtain its interfaces
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
pGraph->QueryInterface(IID_IMediaControl, (void**)&pMediaControl);
pGraph->QueryInterface(IID_IMediaEventEx, (void**)&pEvent);
pGraph->QueryInterface(IID_IVideoWindow, (void**)&pVidWindow);
.
.
.
pMediaControl->Release();
pEvent->Release();
pVidWindow->Release();
pGraph->Release();
CoUninitialize();
return msg.wParam;
}
Add the following to the InitInstance function
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
.
.
.
::hWnd = hWnd;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
Add the following to window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
//Initialize the openfilename structure to be used with GetOpenFileName
OPENFILENAME ofn;
memset(&ofn, 0, sizeof(OPENFILENAME));
ofn.lpstrFile = szMediaFileName;
ofn.nMaxFile = 256;
ofn.lpstrFilter = szFilterString;
ofn.lpstrDefExt = "*.avi";
ofn.lStructSize = sizeof(OPENFILENAME);
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_OPEN:
if(GetOpenFileName(&ofn) == IDOK)
{
//Convert filename from ascii to wide char unicode
MultiByteToWideChar(0, 0, szMediaFileName,
strlen(szMediaFileName), szWMediaFileName, 256);
//Null terminate the widechar string
szWMediaFileName[strlen(szMediaFileName)]=0;
pGraph->RenderFile(szWMediaFileName, NULL);
//Set the video window properties
//Make the video window a child of our application window
pVidWindow->put_Owner((OAHWND)hWnd);
pVidWindow->put_WindowStyle(WS_CHILD|WS_CLIPSIBLINGS);
//Get the dimensions of our client area
RECT rect;
GetClientRect(hWnd, &rect);
pVidWindow->SetWindowPosition(0, 0, rect.right,
rect.bottom);
}
break;
case IDM_PLAY:
if(pMediaControl)
{
pMediaControl->Run();
}
break;
case IDM_STOP:
if(pMediaControl)
{
pMediaControl->Stop();
}
break;
.
.
.
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_SIZE:
pVidWindow->SetWindowPosition(0, 0, LOWORD(lParam), HIWORD(lParam));
break;
case WM_PAINT:
Explanation:
We begin with a brief digression on DirectShow components. DirectShow supports a
number of components, which are useful in the creation of multimedia applications. Each
of these components supports a number of interfaces. The interface of the component can
be said to be the sum-total of all the interfaces supported by it.
The directive
#include <dshow.h>
includes the DirectShow header file, bringing in all the interface and component
declarations.
The lines
IGraphBuilder *pGraph;
IMediaControl *pMediaControl;
IMediaEventEx *pEvent;
IVideoWindow *pVidWindow;
declare variables of interfaces. In this example we work with 4 interfaces of the Filter
Graph Manager class.
IGraphBuilder;
IMediaControl;
IMediaEventEx;
IVideoWindow;
The basic DirectX facilities, such as DirectDraw, DirectSound, and Direct3D, for the
most part are accessed through one interface, IDirectDraw, IDirectSound, and IDirect3D
respectively. DirectShow is a little more complex than this. In a typical application using
DirectShow, you may have 3 or more interfaces you need in order to control your filter
graph. Here's a list of the most common interfaces, which we will be using:
IGraphBuilder - This is the most important interface. It provides facilities to create a
graph for a specific file, and to add/remove filters individually from the graph.
IMediaControl - This interface provides us with methods to start and stop the filter
graph.
IMediaEventEx - This interface is used for setting up notifications that will be sent to
the app when things happen in the graph (reach end of media, buffer under runs, etc.)
IMediaPosition - This interface will let us seek to certain points in our media, let us set
playback rates and other useful bits.
IVideoWindow –This interface is used for controlling DirectShow’s video display
window.
All these interfaces are implemented by the “Filter Graph Manager” component, which is
provided by DirectShow.
We need to initialize the COM library before we can use any COM components. Direct
Show library is a collection of COM components.
We use
CoInitialize(NULL);
to initializes the COM library.
The next step is to create the Filter Graph Manager component.
In COM, components are identified by 128-bit numbers called “class identifiers”
(CLSID). Luckily for us these numbers are defined as constants with a “CLSID_” prefix.
The identifier for Filter Graph Manager is called CLSID_FilterGraph.
Similarly, interface identifiers are also identified by 128-bit numbers called “interface
identifiers” (IID). These identifiers posses a “IID_” prefix.
COM components implement one or more interfaces. While creating the component, we
also need to specify which “one” out of these interfaces we are interested in at the
moment, for eg. IID_IGraphBuilder.
We use the COM library function CoCreateInstance to create the Filter Graph
Component as follows
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
Note that the last parameter is the address to our pGraph variable. In effect we are
passing a pointer to a pointer. This is required in order to receive the interface pointer.
CoCreateInstance creates an instance of the Filter Graph Manager and returns a pointer to
the IGraphBuilder on it.
Next we obtain pointers to the Media Control and Media Event interfaces implemented
by the same instance of the Filter Graph Manager. We use the COM Library’s
QueryInterface function for this purpose. Note that we identify the required interfaces
using IID_.
pGraph->QueryInterface(IID_IMediaControl, (void**)&pMediaControl);
pGraph->QueryInterface(IID_IMediaEventEx, (void**)&pEvent);
pGraph->QueryInterface(IID_IVideoWindow, (void**)&pVidWindow);
This application is a windows application and responds to menus COMMAND messages.
We have earlier defined menu command identifiers while creating the menus using the
menu editor. Windows uses those identifiers to notify us about the command selected.
We perform appropriate action like open, play or stop in the WM_COMMAND part of
the Window Procedure.
In this tutorial we let the user specify the media file to be played. We use windows
GetOpenFileName function to pop-up the standard file-open dialog to assist us in getting
user input. It has only one parameter-a pointer to OPENFILENAME structure that needs
to be filled as shown in program.
Note that RenderFile only accepts WCHAR strings, so we have to convert if we are using
ANSI char strings. We use MultiByteToWideChar to maps a character string to a wide-
character string.
Once we know what file we wish to play, we instruct the Filter Graph Manager to create
a default filter graph using the IGraphBuilder::Render function
IVideoWindow interface is used for controlling DirectShow’s video display window.
Ordinarily, when asked to render a video file DirectShow pop up its own video window.
We use the IVideoWindow interface in order to merge the DirectShow window with our
applications window hierarchy. In this example we make the DirectShow window a child
window of our application main window and resize it to fill the entire window client area.
We do this by using the following lines of code.
//Make the video window a child of our application window
pVidWindow->put_Owner((OAHWND)hWnd);
pVidWindow->put_WindowStyle(WS_CHILD|WS_CLIPSIBLINGS);
//Get the dimensions of our client area
RECT rect;
GetClientRect(hWnd, &rect);
pVidWindow->SetWindowPosition(0, 0, rect.right, rect.bottom);
The line
pMediaControl->Run();
plays the media file by running all the filters in the filter graph, and the line
PMediaControl->Stop();
stops the playback.
Finally, we release all interfaces we have acquired on the Filter Graph Manager. This is
conceptually equivalent to calling delete in C++. This is a required step.
pMediaControl->Release();
pEvent->Release();
pVidWindow->Release();
pGraph->Release();
Before returning from main, we need to signal to the COM library that we are done and
uninitialize it:
CoUninitialize();
You are now ready to build and run your program. Note this particular application can
play only one file at a time. It needs to be restarted for playing a second media file.
The source code is available at location at top of this file.
Go ahead and try it out.