This article first appeared in Visual Developer Magazine.
(Note: This is my unedited original and may differ slightly from the published
version.)
The Windows Media Control Interface, or MCI, is often the first thing a new programmer attempts to use. Playing a sound, recording a CD track, or showing a video clip isn't quite as easy as drawing a control on a form and setting a few properties, but it isn't as difficult as it seems at first glance. This article will teach you the basics of MCI and other Windows audio interfaces via a Visual Basic sample project that records audio clips and plays CDs, audio clips, and video clips.
There are two basic API functions that access the MCI, mciSendString and mciSendCommand. They do the same thing: one with command strings and one with command constants. This article uses strictly the command strings because they make the program easy to read. A device identifier such as "movie" is easier to understand than lDevID.
The mciSendString API function has four parameters:
Public Declare Function mciSendString Lib "winmm.dll" Alias _
"mciSendStringA" (ByVal lpstrCommand As String, _
ByVal lpstrReturnString As String, _
ByVal uReturnLength As Long, _
ByVal hwndCallback As Long) As Long
The lpstrCommand parameter is the actual MCI command you want to execute
(see Table 1). The other three parameters are necessary only when you want to
receive information back from the function. The lpstrReturnString
parameter is a string buffer initialized to uReturnLength characters.
The hwndCallBack variable is a handle to a callback window. This article
doesn’t use callback windows since they are of limited use in MCI functions.
You can subclass a form to watch for the MCI_NOTIFY messages, but there is not
a lot of information in them – the aborted, failure,
successful, and superseded notification messages are not as
useful as the return value of the API function itself. If the result of
mciSendString is non-zero, then you can easily discover exactly what the
error was.
Table 1 – MCI Command Strings
Command Description
capability Returns capability information
capability cdaudio can eject
close Closes an MCI device
close cdaudio
info Returns device information
info cdaudio identity
open Opens an MCI device
open sequencer!filename.mid alias midi
pause Pauses playing or recording
pause cdaudio
play Begins playing an open device
play movie from 0 to 1
put Defines the area and window used for display
put movie window client at 0 0 100 100
record Begin recording
record capture overwrite
save Saves recorded data
save capture filename.wav
set Establish control settings
set capture channels 2
status Returns info on the current status of an MCI device
status cdaudio position
stop Halt playing or recording
stop capture
sysinfo Returns information about the MCI system
sysinfo cdaudio quantity open
window Controls the display window
window movie stretch
Public Function CString(aStr As String) As String
Dim k As Long
k = InStr(aStr, Chr$(0))
If k Then
CString = Left$(aStr, k - 1)
Else
CString = aStr
End If
End Function
The sample application uses the custom MCIString function to actually
execute all MCI commands.
Private Function MCIString(aCommand As String, _
Optional aRet As String = vbNullString, _
Optional bUpdateText As Boolean = True) As Boolean
Dim lRet As Long
lRet = mciSendString(aCommand, aRet, Len(aRet), 0)
If lRet = MMSYSERR_NOERROR Then MCIString = True
If Not bUpdateText Then Exit Function
With txtStatus
If lRet = 0 Then
If Not bNoStatus Then
If Len(.Text) Then .SelText = vbCrLf
.SelText = aCommand & " successful"
End If
Else
If Len(.Text) Then .SelText = vbCrLf
.SelText = aCommand & " ERROR - "
MCIError lRet
End If
End With
End Function
The procedure checks the return value and adds a "success" or
"error" message to the status text box at the bottom of the form (see
Figure 1).
Public Function MCIError(lErr As Long)
Dim a As String, lRet As Long
a = Space$(255)
lRet = mciGetErrorString(lErr, a, 255)
With Form1.txtStatus
If Len(CString(a)) Then
If Len(.Text) Then .SelText = vbCrLf
.SelText = " " & CString(a)
End If
End With
End Function

The Form_Load event of the sample detects all available CD-ROM drives, retrieves all input device names, and enumerates installed codecs (see Code Listing). The first block of code calls the GetDriveType API function with each drive letter to discover CD-ROM drives. An option button is added to the CD Player tab indicating the drive. The drive letter is used as the device identifier for MCI CD audio commands.
The second block of code opens the default mixer device and retrieves the name of every mixer device that supports wave audio input. These devices will later be muted or unmuted depending on the Recording Source selection on the Audio Recording tab.
The final block of code only partially exists in the Form_Load event. The rest is in the modMCI.bas module (see Code Listing). The API functions used here are part of the Audio Control Manager (ACM) interface. All installed codecs are enumerated (listed) via the acmDriverEnum API function. The codecs are in turn opened (acmDriverOpen) and the available format tags for that driver are enumerated. A format tag is assigned by Microsoft to identify compression / decompression formats. The most common format is PCM (pulse-coded modulation). You may also see MPEG Layer 3, Indeo IAC2, or DSP TrueSpeech. The format tag is used with the set MCI command, but many codecs can only be used with the low-level audio interface (waveInOpen, waveInStart, etc…)
When a particular codec and format is selected, the NodeClick event of
the TreeView control is fired. The program checks to see if the selected node
is an actual codec format or just the driver name (see Code Listing). If the
node is an actual format, it enumerates available audio quality settings using
the acmFormatEnum API function (see Figure 2). This loads a ComboBox
with available settings (such as 8,000 kHz, 8 Bit, Mono). Note that even if the
MCI supports the codec, it might not support all quality settings. When loading
the ComboBox with a setting, a global memory block is allocated to store the
format information needed to specify that setting. The pointer to the memory
block is stored in the ItemData property of each List item (see
Code Listing). The memory blocks are freed before closing the application or
modifying the ComboBox with a new codec.

MCIString "open D: type cdaudio" ‘ open CD-ROM D: MCIString "play D:" ‘ play MCIString "close D:" ‘ stop and closeWhen you open a file, place an exclamation plus the filename after the device type. You should use short filenames because a space in the filename is invalid. Assign an "alias" to the opened device so it can be easily referenced throughout your code:
MCIString "open sequencer!filename.mid alias sound" MCIString "open waveaudio!filename.wav alias sound" MCIString "open AVIvideo!filename.avi alias movie"Note that since I specified a drive letter rather than a device type with the CD-ROM, I also had to define the device type. If you have only one CD-ROM attached to the computer, you can also use:
MCIString "open cdaudio" MCIString "play cdaudio" MCIString "close cdaudio"You can track the current play position using the status command. Each tab in the sample has its own Timer control that updates the relevant progress indicators. The ProgressBar indicators are set by retrieving the length of the file, CD track, or CD:
MCIString "status sound length", aand dividing that by the current position:
MCIString "status sound position", b pbAStatus.Value = Int((Val(CString(b)) / Val(CString(a))) * 100)The elapsed time indicators are updated by first setting the time format:
MCIString "set sound time format milliseconds"and then retrieving the current position and formatting it for output:
MCIString "status sound position", a
j = Val(CString(a))
lblAStatus.Caption = Format(Int(j / 60000), "00") & ":" & _
Format(Int((j Mod 60000) / 1000), "00")
Note that I’ve found that the MCI sometimes returns the position and length
information incorrectly. The ProgressBar is updated as a percentage of the
total length, but the milliseconds returned by the position and length
status commands appear to be meaningless.Another quirk I noticed was that the individual frames of a video clip don’t always fall exactly on the millisecond position indicated by the frames per second information returned by the status movie nominal frame rate command. The Video Player tab includes fast forward and rewind buttons that modify the play position based on the frame rate information (see Figure 3). Sometimes a single frame’s time in milliseconds is all that is needed to adjust the frame position, but it often takes two frame’s worth. The routine that moves a frame is therefore a loop that double checks the position:
Private Sub cmdAVIFF_Click()
Dim a As String, b As String, c As String, k As Long
a = Space$(64)
c = a
MCIString "status movie length", c, False
MCIString "status movie position", a, False
b = a
k = Val(CString(a)) + lAVIFrameRate
Do
If tmr(4).Enabled Then
MCIString "play movie from " & k
Else
MCIString "play movie from " & k & " to " & k
End If
MCIString "status movie position", a, False
If a = b Then
k = k + lAVIFrameRate
If k > Val(CString(c)) Then Exit Do
Else
Exit Do
End If
Loop
tmr_Timer 4
End Sub

Recording a Wave
The main trick this program uses to record a wave is selecting an input device.
You can also handle this manually by accessing the audio mixer and clicking the
mute buttons of the recording sources. See Code Listing to learn how the mute controls
are programatically selected and recording is started. Recording continues
until the stop command is issued. The file is saved with the save
command:
Private Sub cmdStopRecord_Click()
cmdRecord.Enabled = True
cmdStopRecord.Enabled = False
cmdSyncStart.Enabled = True
MCIString "stop capture"
DoEvents
tmr(3).Enabled = False
Dim k As Long, aShort As String, a As String
aShort = Space$(MAX_PATH)
Open txtRecord.Text For Binary As 1
Close 1
k = GetShortPathName(txtRecord.Text, aShort, MAX_PATH)
aShort = Left$(aShort, k)
MCIString "save capture " & aShort
MCIString "close capture"
If bSync Then cmdCDStop_Click
bSync = False
End Sub
Don’t issue the close command until after the wave file has been
saved or it will be lost. Note that the file is opened for Binary then
immediately closed. This is so that any long filename entered will be used even
though the MCI requires short filenames.General-purpose applications usually have little need for audio and video. However, adding simple video clips and audio cues to your application is easy when you know how to use the MCI.