Table of Contents
Playing a media file using Direct Show
by Richard Russell, December 2007
Windows provides several methods for playing audio and video files. One of the simplest is the Media Control Interface (MCI) which is used by BBC BASIC for Windows to play MIDI files and is also the basis of the PLAYER.BBC media player application which can be found here. However, whilst MCI is easy to use it does not support some file formats such as animated GIFs.
An alternative is to use Direct Show (a subset of the DirectX system). This is not as straightforward as MCI in terms of code, but it may support a wider range of file formats. This article describes the simplest use of Direct Show to play an audio or video file.
Firstly we need to perform some initialisation:
SYS "LoadLibrary", "OLE32.DLL" TO ole32% SYS "GetProcAddress", ole32%, "CoInitialize" TO `CoInitialize` SYS "GetProcAddress", ole32%, "CoUninitialize" TO `CoUninitialize` SYS "GetProcAddress", ole32%, "CoCreateInstance" TO `CoCreateInstance` SYS "GetProcAddress", ole32%, "CLSIDFromString" TO `CLSIDFromString` SYS `CoInitialize`, 0
To ensure the necessary 'clean up' operations are carried out on exit the program should include ON ERROR and ON CLOSE statements:
ON ERROR PROCcleanup : SYS "MessageBox", @hwnd%, REPORT$, 0, 48 : QUIT ON CLOSE PROCcleanup : QUIT
The next step is to instantiate the filter graph manager:
CLSID_FilterGraph% = FNguid("{e436ebb3-524f-11ce-9f53-0020af0ba770}") IID_IGraphBuilder% = FNguid("{56a868a9-0ad4-11ce-b03a-0020af0ba770}") CLSCTX_INPROC_SERVER = 1 SYS `CoCreateInstance`, CLSID_FilterGraph%, 0, CLSCTX_INPROC_SERVER, \ \ IID_IGraphBuilder%, ^m_graphBuilder% TO hr% IF hr% THEN ERROR 100, "Could not create filter graph manager"
Once the filter graph manager COM object is instantiated we can use it to create two other DirectShow objects, firstly a media control object:
IID_IMediaControl% = FNguid("{56a868b1-0ad4-11ce-b03a-0020af0ba770}") SYS !(!m_graphBuilder%), m_graphBuilder%, IID_IMediaControl%, ^m_mediaControl% TO hr% IF hr% THEN ERROR 100, "Could not instantiate media control object"
and secondly a media event object:
IID_IMediaEvent% = FNguid("{56a868b6-0ad4-11ce-b03a-0020af0ba770}") SYS !(!m_graphBuilder%), m_graphBuilder%, IID_IMediaEvent%, ^m_mediaEvent% TO hr% IF hr% THEN ERROR 100, "Could not instantiate media event object"
If the above code succeeded we now have all three Direct Show objects required to handle streamed audio or video playback.
To play a file we need to create a filter graph:
SYS !(!m_mediaControl%+44), m_mediaControl%, FNwide(mediafile$), 0 TO hr% : REM Render() IF hr% THEN ERROR 100, "Could not render file "+mediafile$
Here mediafile$ should be a fully-qualified path and filename of the file to be played.
Now everything is ready to play the file:
SYS !(!m_mediaControl%+28), m_mediaControl% TO hr% : REM Run() REM Wait for the playback to complete: timeout% = 100 REPEAT SYS !(!m_mediaEvent%+36), m_mediaEvent%, timeout%, ^pEvCode% : REM Wait UNTIL pEvCode%
You can carry out other operations while the file is playing by including them in the above waiting loop.
To play another file, you should release the three objects as in PROCcleanup below (but not call CoUninitialize) and then instantiate the three objects again starting with the filter graph manager. It is preferable to call CoInitialize just once at the very start of the program, and CoUninitialize just once on exit.
Before exiting the program you should call the 'clean up' routine:
PROCcleanup QUIT DEF PROCcleanup m_mediaEvent% += 0 : IF m_mediaEvent% SYS !(!m_mediaEvent%+8), m_mediaEvent% m_mediaControl% += 0 : IF m_mediaControl% SYS !(!m_mediaControl%+8), m_mediaControl% m_graphBuilder% += 0 : IF m_graphBuilder% SYS !(!m_graphBuilder%+8), m_graphBuilder% SYS `CoUninitialize` ENDPROC
Should you wish to pause or stop playback prematurely you can do so using the following code segments:
REM Pause playback: SYS !(!m_mediaControl%+32), m_mediaControl% TO hr% : REM Pause() REM Stop playback: SYS !(!m_mediaControl%+36), m_mediaControl% TO hr% : REM Stop()
Finally here are the functions FNwide and FNguid used by the above code:
DEF FNwide(A$) LOCAL M$ M$ = STRING$(2*LENA$+2," ") SYS "MultiByteToWideChar", 0, 0, A$, -1, !^M$, LENA$+1 = M$ DEF FNguid(A$) LOCAL C%, M% DIM C% 15, M% LOCAL 2*LENA$+1 SYS "MultiByteToWideChar", 0, 0, A$, -1, M%, LENA$+1 SYS `CLSIDFromString`, M%, C% = C%
Fullscreen output
To display a video file fullscreen, add the following code immediately before the call to Run():
REM Instantiate a video window object: IID_IVideoWindow% = FNguid("{56a868b4-0ad4-11ce-b03a-0020af0ba770}") SYS !(!m_graphBuilder%), m_graphBuilder%, IID_IVideoWindow%, ^pVW% TO hr% IF hr% THEN ERROR 100, "Could not instantiate video window object" REM Define the IVideoWindow interface: DIM IVideoWindow{QueryInterface%, AddRef%, Release%, GetTypeInfoCount%, \ \ GetTypeInfo%, GetIDsOfNames%, Invoke%, put_Caption%, get_Caption%, \ \ put_WindowStyle%, get_WindowStyle%, put_WindowStyleEx%, get_WindowStyleEx%, \ \ put_AutoShow%, get_AutoShow%, put_WindowState%, get_WindowState%, \ \ put_BackgroundPalette%, get_BackgroundPalette%, put_Visible%, get_Visible%, \ \ put_Left%, get_Left%, put_Width%, get_Width%, put_Top%, get_Top%, \ \ put_Height%, get_Height%, put_Owner%, get_Owner%, \ \ put_MessageDrain%, get_MessageDrain%, get_BorderColor%, put_BorderColor%, \ \ get_FullScreenMode%, put_FullScreenMode%, SetWindowForeground%, \ \ NotifyOwnerMessage%, SetWindowPosition%, GetWindowPosition%, \ \ GetMinIdealImageSize%, GetMaxIdealImageSize%, GetRestorePosition%, \ \ HideCursor%, IsCursorHidden%} !(^IVideoWindow{}+4) = !pVW% REM Set fullscreen mode: SYS IVideoWindow.put_FullScreenMode%, pVW%, TRUE TO hr% IF hr% THEN ERROR 100, "Could not set fullscreen mode"
To close the fullscreen window, use the following code:
WM_CLOSE = &10 SYS "FindWindow", 0, "ActiveMovie Window" TO hamw% IF hamw% SYS "SendMessage", hamw%, WM_CLOSE, 0, 0
Windowed output
To display a video file in a window (whose size and position you can specify), add the following code immediately before the call to Run():
REM Instantiate a video window object: IID_IVideoWindow% = FNguid("{56a868b4-0ad4-11ce-b03a-0020af0ba770}") SYS !(!m_graphBuilder%), m_graphBuilder%, IID_IVideoWindow%, ^pVW% TO hr% IF hr% THEN ERROR 100, "Could not instantiate video window object" REM Define the IVideoWindow interface: DIM IVideoWindow{QueryInterface%, AddRef%, Release%, GetTypeInfoCount%, \ \ GetTypeInfo%, GetIDsOfNames%, Invoke%, put_Caption%, get_Caption%, \ \ put_WindowStyle%, get_WindowStyle%, put_WindowStyleEx%, get_WindowStyleEx%, \ \ put_AutoShow%, get_AutoShow%, put_WindowState%, get_WindowState%, \ \ put_BackgroundPalette%, get_BackgroundPalette%, put_Visible%, get_Visible%, \ \ put_Left%, get_Left%, put_Width%, get_Width%, put_Top%, get_Top%, \ \ put_Height%, get_Height%, put_Owner%, get_Owner%, \ \ put_MessageDrain%, get_MessageDrain%, get_BorderColor%, put_BorderColor%, \ \ get_FullScreenMode%, put_FullScreenMode%, SetWindowForeground%, \ \ NotifyOwnerMessage%, SetWindowPosition%, GetWindowPosition%, \ \ GetMinIdealImageSize%, GetMaxIdealImageSize%, GetRestorePosition%, \ \ HideCursor%, IsCursorHidden%} !(^IVideoWindow{}+4) = !pVW% REM Set the video window's owner to BB4W's window: SYS IVideoWindow.put_Owner%, pVW%, @hwnd% TO hr% IF hr% THEN ERROR 100, "Could not set owner window" REM Set the style of the video window: WS_CHILD = &40000000 WS_CLIPSIBLINGS = &4000000 SYS IVideoWindow.put_WindowStyle%, pVW%, WS_CHILD OR WS_CLIPSIBLINGS TO hr% IF hr% THEN ERROR 100, "Could not set window style" REM Set the video window size and position: SYS IVideoWindow.SetWindowPosition%, pVW%, left%, top%, right%, bottom% TO hr% IF hr% THEN ERROR 100, "Could not set window rect"
To close the video window, use the following code:
WM_CLOSE = &10 SYS "FindWindow", 0, "ActiveMovie Window" TO hamw% IF hamw% SYS "SendMessage", hamw%, WM_CLOSE, 0, 0