Download Source Code and
Example
Introduction. What is the MCI interface?
The MCI (Media Control Interface) is the
Windows API that gives programmer a high-level control on all
multimedia devices and resource files. MCI provides applications
with device-independent capabilities for controlling audio, video
and visual peripherals. This API has two different interface
levels: at the lower level (command-message interface)
the programmer controls devices by calling the mciSendCommand()
function. This function requires as its arguments the command to
be sent and some other command-specific parameters. The higher
level (command-string interface) is essentially a
textual version of the first, meaning that each command is
passed, together with its possible arguments, as a text string to
the mciSendString() function. As you can easily
imagine, the higher level offers a more friendly interface,
keeping programmers away from flags and structures. Even if MCI
is quite simple to use, I think that it should be a good thing if
it was object-oriented, too.
In this article, I’ll try to explain how to build a simple
CD-audio player, using my CCdAudio class. This
class encapsulates the MCI command-message interface for
CD-audio. I hope it can be a good starting point to write
analogous classes for other multimedia devices: waveform-audio
devices, MIDI sequencers, etc. Building the player, however,
involves many other interesting issues. In particular, I’ll show
- How to use the asynchronous mode of MCI
commands: the MM_MCINOTIFY message. - How to detect the events of opening and
closing the CD drawer. - How to use the MSF and TMSF time formats.
Wrapping the MCI
Writing a set of classes to encapsulate the whole
MCI interface isn’t so simple as you can think. The main
difficulty arises from the fact that it hasn’t been thought in an
object-oriented way. The best thing is to start writing a base
class that implement a generic MCI device: this will be the base
class for all the other specific devices (Fig. 1).
CMciDevice overview
I called this base class CMciDevice. Its SendCommand()
protected member function hides the mciSendCommand() function,
but has also the capability to handle possible errors: when the
device is in the ‘error report’ mode, MCI errors are
automatically displayed using the MessageBox() function. You can
turn the error report on or off with the ReportErrors()
member function (the default is no error report).
In the CMciDevice class, as well as in CCdAudio, many of the
functions that implements MCI commands return a DWORD value that
is zero in case of success and a nonzero error code otherwise .
You can use this value to perform your own error handling (if you
want to know more about MCI error codes you can take a look at
the documentation). Other functions, however, don’t return any
error code. In these cases, you can use the GetLastError()
function to get the code of the last occurred error.
The GetDevCaps()
function ‘queries’ the device to know if it has specific
capabilities (see the table below).
The GetdevcapsCompound
capability deserves some more explanation. MCI devices are
divided in two categories: simple and compound
devices. The difference is that a compound device needs a media
file to work. Cd-audio is a typical simple device, while
wave-recorder is a compound device since it needs a file (the
waveform) to work. For more details on CMciDevice you can go to
the reference guide
below.
Synchronous vs asynchronous
method
MCI supports both the synchronous and asynchronous operating
mode. When a MCI operation is performed synchronously the mciSendCommand()
function returns only when its task has been carried out. In the
asynchronous mode, instead, the function returns immediately but
a ‘callback’ window has to be designated by passing its handle as
a parameter. When the required operation has been performed (with
or without success), MCI notifies the callback window of the
event sending it a MM_MCINOTIFY message. This
message has a parameter whose values can be used to understand if
the required operation has been successfully performed, aborted
or failed (see the sample project code for details about how to
do this). Asynchronous method is very convenient for long
operations that might require much time to be completed such as
playback or seek. This is the reason why, in CCdAudio, Play() and Seek() are
asynchronous by default.
Detecting the CD drawer
opening and closing
When you open the CD drawer or when you insert a new CD and
close it, Windows sends applications a special
"plug-and-play" message: WM_DEVICECHANGE.
This message notifies an application or device driver of a change
to the hardware configuration of a device or the computer. Its
parameters are:
Event = (UINT) wParam;
dwData = (DWORD) lParam;
The opening of the CD drawer is signalled by the operating
system as a DBT_DEVICEREMOVECOMPLETE event. Its closing, instead,
is a DBT_DEVICEARRIVAL event. The dwData parameter points to an
event-specific structure, but DEV_BROADCAST_HDR is a structure
header common to all the structure types that dwData can point.
The dbch_devicetype field distinguishes ports from logical
devices: if it is equal to DBT_DEVTYP_VOLUME then dwData points
to a DEV_BROADCAST_VOLUME structure. In this structure, the
dbcv_flags field shows if the involved drive is a network drive
or a physical device. To verify if it is really a CD we have to
use the dbcv_unitmask field: it is an unsigned long value with a
set bit for each drive that caused the event: the first
(leftmost) bit for A, the second for B, and so on. For instance,
if the CD drive is D: its value will be 8 (= 0…01000) since D
is the fourth letter. Once we have obtained the drive letter,
reading the volume label and verifying if it is really a CD drive
is quite simple. Sample code follows:
BOOL CMCISampleDlg::OnDeviceChange( UINT nEventType, DWORD dwData ) { DEV_BROADCAST_HDR *pdbch = (DEV_BROADCAST_HDR *) dwData; switch(nEventType) { case DBT_DEVICEARRIVAL: // CD drawer was closed if (pdbch->dbch_devicetype == DBT_DEVTYP_VOLUME) { PDEV_BROADCAST_VOLUME pdbcv = (PDEV_BROADCAST_VOLUME) dwData; if (pdbcv->dbcv_flags == DBTF_MEDIA) { TCHAR szDrive[4]; // pdbcv->unitmask contains the drive bits for (UINT i=0; !(pdbcv->dbcv_unitmask & (1<<i)); i++); wsprintf(szDrive, _T("%c:\\"), 'A'+i); if (GetDriveType(szDrive) == DRIVE_CDROM) { UpdateControls(); } } else { // It is a network drive } break; } case DBT_DEVICEREMOVECOMPLETE: // CD drawer was opened break; } return TRUE; }
Several MCI commands involve setting or getting a time. For
instance, the Play()
function requires two times: the time at which to start the
playback and the time at which to stop it. Both are DWORDs but,
however, MCI can interpret them in different ways, depending on
which time format has been previously set with the MCI_SET
command (or, in he CCdAudio class, with the SetTimeFormat()
member function). The allowable time formats for CD-audio are
milliseconds, MSF and TMSF. MSF stands for Minutes/Second/Frame
while TMSF stands for Track/Minute/Second/Frame. MSF and TMSF
times are packed in a single DWORD and special macros are used to
pack/unpack them. To simplify programmer’s life, I wrote two
classes (CMsf and CTmsf ) for these time formats to eliminate the
necessity of remembering (and using) these macros. Both have an
overloaded operator DWORD to allow their use with those functions
that require DWORD parameters. Here are the two classes as they
are defined in the ‘Mci.h’ file:
class CMsf { public: CMsf() { m_dwMsf = 0; } CMsf(DWORD dwMsf) { m_dwMsf = dwMsf; } CMsf(BYTE minute, BYTE second, BYTE frame) { m_dwMsf = MCI_MAKE_MSF(minute, second, frame); } operator DWORD() const {return m_dwMsf;} BYTE GetMinute() const { return MCI_MSF_MINUTE(m_dwMsf); } BYTE GetSecond() const { return MCI_MSF_SECOND(m_dwMsf); } BYTE GetFrame() const { return MCI_MSF_FRAME(m_dwMsf); } protected: DWORD m_dwMsf; }; class CTmsf { public: CTmsf() { m_dwTmsf = 0; } CTmsf(DWORD dwTmsf) { m_dwTmsf = dwTmsf; } CTmsf(BYTE track, BYTE minute, BYTE second, BYTE frame) { m_dwTmsf = MCI_MAKE_TMSF(track, minute, second, frame); } operator DWORD() const {return m_dwTmsf;} BYTE GetTrack() const { return MCI_TMSF_TRACK(m_dwTmsf); } BYTE GetMinute() const { return MCI_TMSF_MINUTE(m_dwTmsf); } BYTE GetSecond() const { return MCI_TMSF_SECOND(m_dwTmsf); } BYTE GetFrame() const { return MCI_TMSF_FRAME(m_dwTmsf); } protected: DWORD m_dwTmsf; };
How to use CCdAudio
To use CCdAudio you have to copy the ‘Mci.h’, ‘Mci.cpp’,
‘CdAudio.cpp’, and ‘CdAudio.h’ files in your project folder. Once
you have copied these files, include ‘Mci.cpp’ and ‘CdAudio.cpp’
in your project. #include the CdAudio.h file at the beginning of
the module that needs it. Be aware that you have to link your
program with the ‘winmm.lib’ static library, or an error will
occur at link-time.
Using any MCI device requires the following steps:
- Open the device using Open().
- Set a callback window for asynchronous operations using SetCallbackWnd()
(optional). - Set a time format using SetTimeFormat()
(optional). - Perform a sequence of operations: (Play(),
Stop(),
Seek(),
… etc.). - Close the device using Close().
Operations for CMciDevice
- void Attach(UINT wDeviceID)
Attaches the device to a MCI device already opened - MCIDEVICEID GetDeviceID() const
Gets the device ID
virtual DWORD Open(DWORD
dwDeviceType, BOOL bShareable = FALSE)
virtual DWORD Open(LPCSTR lpstrName,
BOOL bShareable = FALSE);
Opens the device. if bShareable is set to TRUE
the device can be shared with other applications that use
it. The device can be opened by a device type constant (dwDeviceType)
or by a driver name (lpstrName).
virtual DWORD Close()
Closes the device- static DWORD CloseAll()
Static member function. Closes all MCI devices opened by
the application: wait until devices are closed
MCIERROR GetLastError()
Returns the code of the last error occurred.
DWORD GetDevCaps(DWORD
dwDevcaps, BOOL bItem = FALSE)
Retrieves static information about the device. If bItem
is set to TRUE you can retrieve specific capabilities
(see the table below)- virtual DWORD GetStatus(DWORD
dwStatusItem)
Retrieves information about the device (see the table below) - virtual DWORD GetInfo(DWORD
dwInfoString, LPSTR lpstrReturn, DWORD dwRetSize).
Retrieves a string information from the device
(see the table below); - virtual DWORD GetMode()
Shortcut for GetStatus(CMciDevice::StatusMode) (see the table below) - HWND GetCallbackHwnd() const
Retrieves the handle of the callback window
void SetCallbackWnd(CWnd*
pWnd)
void SetCallbackWnd(HWND hWnd)
Sets the callback Window.
void ReportErrors(BOOL
bReport = TRUE)
Sets error report on/off
Static constants
Values for dwDevcaps GetDevCaps()
(bItem = FALSE)
- DWORD GetdevcapsCompound
- DWORD GetdevcapsHasAudio
- DWORD GetdevcapsHasVideo
- DWORD GetdevcapsHasVideo
- DWORD GetdevcapsUsesFiles
- DWORD GetdevcapsDeviceType
Values for dwDevcaps GetDevCaps()
(bItem = TRUE)
- DWORD GetdevcapsCanEject
- DWORD GetdevcapsCanPlay
- DWORD GetdevcapsCanRecord
- DWORD GetdevcapsCanSave
Values returned by GetDevCaps(CMciDevice::GetdevcapsDeviceType)
- DWORD DevtypeAnimation
- DWORD DevtypeCdaudio
- DWORD DevtypeDat
- DWORD DevtypeDigitalvideo
- DWORD DevtypeOther
- DWORD DevtypeOverlay
- DWORD DevtypeScanner
- DWORD DevtypeSequencer
- DWORD DevtypeVcr
- DWORD DevtypeVideodisc
- DWORD DevtypeWaveaudio
Values for dwStatusItem in GetStatus()
- DWORD StatusReady
- DWORD StatusMediaPresent
- DWORD StatusMode
- DWORD StatusNumberOfTracks
- DWORD ModeNotReady
- DWORD ModePause
- DWORD ModePlay
- DWORD ModeStop
- DWORD ModeStop
- DWORD ModeRecord
- DWORD ModeSeek
Values for dwInfoString
in GetInfo()
- DWORD InfoProduct
Operations for CCdAudio
- DWORD Open(BOOL
bShareable = FALSE)
Opens the device. bShareable has the same
meaning as in CMciDevice::Open() - DWORD PlayTrack(BYTE
bTrack, BOOL bAsync = TRUE)
Plays a CD track asynchronously. If bAsync
is set to FALSE, the playback is performed synchronously.
DWORD Play(DWORD
dwFrom = 0L, DWORD dwTo = 0L, BOOL bAsync = TRUE)
Plays from the position dwFrom to the
position dwTo asynchronously. If bAsync
is set to FALSE, the playback is performed synchronously.
DWORD Stop()
Stops playback- DWORD Pause()
Pauses playback.
DWORD Seek(DWORD
dwTo, BOOL bAsync = FALSE)
Seek to the position dwTo. If bAsync is
set to FALSE, the operation is performed synchronously.
DWORD SeekToStart(BOOL bAsync = FALSE)
DWORD SeekToEnd(BOOL bAsync = FALSE)- DWORD OpenDoor(BOOL
bOpenDoor = TRUE)
Opens/Closes the CD drawer - DWORD GetNumberOfTracks()
Retrieves the number of playable tracks. It is a
shortcut for GetStatus(MciDevice::StatusNumberOfTracks). - DWORD GetCurrentTrack().
Retrieves the current track number. It is a
shortcut for GetStatus(CCdAudio::StatusCurrentTrack). - DWORD GetMediaLenght().
Retrieves the total CD length. It is a shortcut
for GetStatus(CMciDevice::StatusMediaLenght). - DWORD GetStartPos().
Retrieves the CD starting position. It is a
shortcut for GetStatus(CCdAudio::StatusStart). - DWORD GetCurrentPos()
Retrieves the current player position. It is a
shortcut for GetStatus(CCdAudio::StatusPosition). - BOOL IsReady()
Checks if the device is ready to be operated. It
is a shortcut for GetStatus(MciDevice::StatusReady). - DWORD GetTrackLength(DWORD
dwTrack)
Retrieves the length of the CD track dwTrack. - DWORD GetTrackPos(DWORD
dwTrack)
Retrieves the starting position of the CD track dwTrack. - DWORD GetTrackType(DWORD
dwTrack)
Retrieves the type of the CD track dwTrack (see
the table below). - DWORD GetTimeFormat()
Retrieves the current time format (see the table below).
DWORD SetTimeFormat(DWORD
dwTimeFormat)
Sets a new time format for the device (see the table below)..
Static constants
Device specific time
formats for dwTimeFormat in SetTimeFormat()
- DWORD FormatMilliseconds
- DWORD FormatMSF
- DWORD FormatTMSF
Device-specific status
items for GetStatus()
- DWORD StatusCurrentTrack
- DWORD StatusLenght
- DWORD StatusPosition
- DWORD StatusStart
Values returned by GetTrackType()
- DWORD TrackTypeAudio
- DWORD TrackTypeOther
Values for dwInfoString
in GetInfo()
- DWORD InfoProduct
- DWORD InfoMediaIdentity
- DWORD InfoMediaUPC
What’s new in the latest release?
- Some Getdevcap constant in CMciDevice was
added. - Some bug fix in the CMciSendCommand
function: in the precedent version the error report
doesn’t work fine. Now it works well also with the
CloseaAll() static function. - The constant InfoProduct was added in
CCdAudio
Author’s note
This is a work in progress: I ‘m continuously working to
improve it. I’ll be grateful to you if you mail me your comments,
advice, or bug apparition reports!.
Last updated: 4 June 1998