The Basics to Using DirectShow
Preface
Before I begin, this article is not going to get too intensive in the use of DirectShow. DirectShow has a lot of potential, but since many of it's features which could be used with gaming are so obscure or just plain useless and complex, I will not really touch on them. Instead, I will focus mainly on setting up DirectShow and achieving basic use which could produce good game functionality.
As far as there being a future for this article, as I have seen no other articles which touch specifically on DirectShow there is a good chance I will extend on this furthur in the near, and possibly later future, getting into more detail in each additional article.
This article, although not necessarily limited to it, will be written with the DirectX 8.0 API in mind, including the DirectShow API which is not distributed by default with DirectX 8.
Introduction to DirectShow
Prior to DirectX 8.0 you would to have downloaded DirectShow as a seperate SDK, although I will be dealing with the DirectX 8 release of DShow. To use this article, you should have some primitive comprehension of how the COM interface works.
Without DirectShow it would be difficult, although not impossible, to create a video and play it to a screen. One possibility would be to create a 3D object and render an audio-visual interleave as a texture. This, however, creates a problem with playing the audio, and synching the two sources when necessary. With DirectShow, all you have to do is create the control interfaces, at the very minimum, in which you can play the video. DirectShow will then take care of splitting the video and audio, synching it, and handling most events which could arise within the playback of the file. Of course one could attempt to write their own timers, and library to render, play, and synch the video with the audio on a DirectDraw surface, or in any other manner, but this would be a hassle when DirectShow already provides that functionality.
The Very Basics
The very first thing you are going to want to do is to create a windows application which includes a window layout, registers the callback, and registers the window class. To use DirectShow you will require that dshow.h is included in the source file, and that the project is linked to strmiids.lib
Next, we will need to create a few globals which we will use to control the playback and create the interface to playback with.
You should get this:
source.cpp:
#include <windows.h> #include <dshow.h> //Globals: static IGraphBuilder *pGB = NULL; static IMediaControl *pMC = NULL; static IVideoWindow *pVW = NULL; static IMediaEventEx *pME = NULL; static HWND g_hwnd = 0; ?
Next we will need to start up our COM interface. Within WinMain you would need to add this command:
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int mCmdShow){ CoInitialize(NULL); ? }
NOTE: When you are done, you would call CoUninitialize(); to shut it down again.
Now we need to create the component interface in which we will initialize all other objects and eventually play the file. To initialize DirectShow you make this call:
//This creates the filter graph manager CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGB); //Query COM interface pGB->QueryInterface(IID_IMediaControl, (void **)&pMC); pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW);
Next we will build the graph. Here we just pass the file name in which we would like to play to the command and DirectShow will take care of connecting all the filters for the file to play it.
pGB->RenderFile(L"test.avi",NULL);
NOTE:
- The "L" is used to convert the filename from an ASCII string to a Wide Character string.
- The only compression types of playable media thus far supported by DirectShow are:
- Microsoft Windows Media Video codec version 7.0*
- ISO MPEG-4 video version 1.0*
- Microsoft MPEG-4 version 3*
- Sipro Labs ACELP*
- Windows Media Audio*
- MPEG Audio Layer-3 (MP3) (decompression only)
- Digital Video (DV)
- MPEG-1
- MJPEG
- Indeo
- Voxware*
- Cinepak
All we have to do now is set the window that DirectShow will create to play the video in as a Child to the window you have already created, and then play the file.
//Set the parents window pVW->put_Owner((OAHWND)g_hwnd); //Sets the child window pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS); //here we create a RECT in which to draw the child window on RECT vwrect; //get the size of the Parent GetClientRect(g_hwnd,&vwrect); //Set the Child to this position pVW->SetWindowPosition(0,0,vwrect.right,vwrect.bottom); //and now the long awaited play pGB->Run();
All you have to do now when the file stops is cleanup.
pVW->put_Visible(OAFALSE); pVW->put_Owner(NULL); pMC->Release(); pVW->Release(); pGB->Release();
Handling Events
This is great, now with the information that was specified, it is possible to load and play a video file to the screen with minimal effort, so long as the compression format of the file is one of those supported by DirectShow. The only problem now remains that when the file plays, the application stops, and, well, nothing else occures. This brings on the question of using event handlers to aid in controlling the playback of the video.
This is where ImediaEventEx comes into play. You may want to revisit the globals uptop and add the following line:
#define WM_P_GRAPHNOTIFY WM_APP + 1
Windows will allow private messages to be specified to the callback function with the values of WM_APP through 0xBFFF. The define above will define one of those callback events.
When initializing the component interfaces, you will have to add the following code:
pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME);
Setting WM_P_GRAPHNOTIFY as a valid windows callback message is just as simple as executing the following line:
pME->SetNotifyWindow((OAHWND)g_hwnd, WM_P_GRAPHNOTIFY, 0);
The first argument would be the window in which to send the messages to, in this case the parent window, also g_hwnd. The second argument is the message that will be sent, and the third argument is which instance of DirectShow to send this for.
Because Windows does not include any useful information in the wparam or lparams at this point, we have to create another function which will check the DirectShow message Queue for us.
case(msg) WM_P_GRAPHNOTIFY: HandleEvent(); break; ... void HandleEvent(){ long evCode, param1, param2; HRESULT hr; while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr)) { hr = pEvent->FreeEventParams(evCode, param1, param2); if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode)) { CleanUp(); break; } } }
In this situation, because Windows messages and DirectShow messages are asynchronous, we will keep checking the queue for messages so there is never a flood or pool of unchecked messages until there is an error, when we know it is empty and safe to stop. Because we know that there is a message waiting for use, when we call GetEvent, we will pass 0 as the fourth argument to specify no timeout. After we retreive an event, we will call FreeEventParams to release any resources used to check the queue.
Conclusion
There are many more methods of applying DirectShow for use om a game, for instance for playing video trailers, cutscenes, or any media, would be that it can be rendered to a FullScreen DirectDraw surface while in Exclusive mode.
Although I have not attempted it at this point, it is in my belief that it is possible to render the video similarly to what I have shown above to a non-exclusive DirectDraw environment. My best guess would be to set the window to the entire screen, and remove the borders; let me know if this works for anyone.
I am greatly considering adding to this article in the future, if you have any comments or questions, email myself at kurifu@aura-games.com. My next idea I think I will discuss playing Fullscreen video in DirectDraw Exclusive mode.
Kurifu Roushu kurifu@aura-games.com
http://www.aura-games.com
链接:http://www.gamedev.net/reference/articles/article1345.asp