A Cd-audio class to build a simple CD player


Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame

The simple CD player dialog 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

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 ) {

	switch(nEventType) {
	case DBT_DEVICEARRIVAL:					// CD drawer was closed
		if (pdbch->dbch_devicetype == DBT_DEVTYP_VOLUME) {
			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) {					
			else {
				// It is a network drive
	case DBT_DEVICEREMOVECOMPLETE:				// CD drawer was opened

	return TRUE;

The MSF and TMSF time formats

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 {
	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); }

	DWORD m_dwMsf;

class CTmsf {
	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); }

	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:

  1. Open the device using Open().
  2. Set a callback window for asynchronous operations using SetCallbackWnd() (optional).
  3. Set a time format using SetTimeFormat() (optional).
  4. Perform a sequence of operations: (Play(), Stop(), Seek(), ... etc.).
  5. Close the device using Close().


Operations for CMciDevice

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

Values returned by GetMode().

  • DWORD ModeNotReady
  • DWORD ModePause
  • DWORD ModePlay
  • DWORD ModeStop
  • DWORD ModeStop
  • DWORD ModeRecord
  • DWORD ModeSeek

Values for dwInfoString in GetInfo()

  • DWORD InfoProduct

Operations for CCdAudio

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


  • Too slowly

    Posted by Legacy on 03/28/2002 08:00am

    Originally posted by: PerduBug

    I founs this applicaion is too slowing.:-(

  • Problem with this player

    Posted by Legacy on 12/19/2001 08:00am

    Originally posted by: malcom

    Today I visited www.codeguru.com and see the Audio CD player in multimedia section. this is really a good player.

    I have some question related to this player.
    My question is > why this player is not synchronized with windows CD player ?like when I run Windows CD player while your CD player is running then windows CD player takes control of CD.and when I press "Stop" or "Pause" on your CD Player then next track start playing on windows CD Player.

    How we can synchronized both CD Player so that pressing "Stop" on one CD Player cd stop playing and pressing "Play" on other CD Player it start playing.


  • Table Of Contents

    Posted by Legacy on 12/16/2001 08:00am

    Originally posted by: John Forester

    How is it possible to get the table of contents (toc) of a mixed mode cd (audio tracks and data tracks) to calculate the entire lead out of the cd.
    The mci interface only recognizes audio tracks.


    Posted by Legacy on 07/16/2001 07:00am

    Originally posted by: Paul Mirhtil

    Do you know how save tracks from an audio CD into WAV files?

  • Problem with mmsystem.h

    Posted by Legacy on 07/11/2001 07:00am

    Originally posted by: Andy

    Hi !

    I am working with VC 6.0, but I have problem with mmsystem.h. It shows compile errors. Is there a problem with mmsystem.h under w2k? Or have I forgoten to do something. I am just recompiling your cd_audio sources.

    Thanks for any hints.

  • Can't get the drive letter using MCI

    Posted by Legacy on 01/24/2001 08:00am

    Originally posted by: Thi Pham

    I cannot obtain the drive letter using MCI (I am successful at getting the device ID). I've tried nearly everything from their online documentation to achieve this. I'm running on MS Windows NT 4.0 Workstation. Could some please e-mail me a solution on how to do this with MCI.

    Thi Pham

  • works only on sound blaster cards

    Posted by Legacy on 12/17/2000 08:00am

    Originally posted by: John Beckert

    A great example! I'v coded my own cd player using the MCI commands as this example but theres one catch. Seems to only work with the sound blaster family cards. I always thought that MCI used the windows API that would be compatible with all cards? Any ideas or answers to code for all sound cards? I have looked and looked through msdn and have found no answers.....

  • mci

    Posted by Legacy on 10/30/2000 08:00am

    Originally posted by: suresh

    i would like to know how to save the tracks obtained fromthe audio cd to the hard disk and waiting for your reply

  • Controlling multiple CD-ROM drives...

    Posted by Legacy on 04/15/2000 07:00am

    Originally posted by: Joshua Smith

    I am one of the ever increasing number of people with multiple CD-ROM drives on their system and I would like to extend the functionality of my program to be able to play CD audio from either of my drives. I looked all through the documentation (MSDN and otherwise) and I traced one of the base open or attach functions to one that wants either a CONSTANT or an id for the drive. The code already uses the CONSTANT and it only allows use of the first drive on my system. I thought that the id would allow me to open a second CD-ROM and so I looked a little further trying to see if it told where to get the id. The documentation said that the id value was in either system.ini or in the registry. I looked all through system.ini and I could not find anything that even resembled a device id. Can someone please point me in the right direction?

    Joshua Smith

  • Setting sound volume

    Posted by Legacy on 04/13/2000 07:00am

    Originally posted by: Yuthasak Buasuwan

    How to set volume?

  • Loading, Please Wait ...

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date