In one sense, multimedia is all about getting access to various pieces of hardware through device-independent function calls. Let's look at this hardware first and then the structure of the Windows multimedia API.
Perhaps the most commonly used piece of multimedia hardware is the waveform audio device, commonly known as the sound card or sound board. The waveform audio device converts microphone input or other analog audio input into digitized samples for storage in memory or disk files with the .WAV extension. The waveform audio device also converts the waveform back into analog sound for playing over the PC's speakers.
The sound board usually also contains a MIDI device. MIDI is the industry standard Musical Instrument Digital Interface. Such hardware plays musical notes in response to short binary messages. The MIDI hardware usually can also accept a cable connected to a MIDI input device, such as a music keyboard. And often external MIDI synthesizers can also be attached to the sound board.
The CD-ROM drive attached to most of today's PCs is usually capable of playing normal music CDs. This is known as "CD Audio." The output from the waveform audio device, MIDI device, and CD Audio device are often mixed together under user control with the Volume Control application.
A couple other common multimedia "devices" don't require any additional hardware. The Video for Windows device (also called the AVI Video device) plays movie or animation files with the .AVI ("audio-video interleave") extension. The ActiveMovie control plays other types of movies, including QuickTime and MPEG. The video board on a PC may have specialized hardware to assist in playing these movies.
More rare are PC users with certain Pioneer laserdisc players or the Sony series of VISCA video cassette recorders. These devices have serial interfaces and thus can be controlled by PC software. Certain video boards have a feature called "video in a window" that allows an external video signal to appear on the Windows screen along with other applications. This is also considered a multimedia device.
The API support of the multimedia features in Windows is in two major collections. These are known as the "low-level" and the "high-level" interfaces.
The low-level interfaces are a series of functions that begin with a short descriptive prefix and are listed (along with high-level functions) in /Platform SDK/Graphics and Multimedia Services/Multimedia Reference/Multimedia Functions.
The low-level wavefrom audio input and output functions begin with the prefix waveIn and waveOut. We'll be looking at these functions in this chapter. Also examined in this chapter will be midiOut functions to control the MIDI Output device. The API also includes midiIn and midiStream functions.
Also used in this chapter are functions beginning with the prefix time that allow setting a high-resolution preemptive timer routine with a timer interval rate going down to 1 millisecond. This facility is primarily for playing back MIDI sequences. Several other groups of functions involve audio compression, video compression, and animation and video sequences; unfortunately, these will not be covered in this chapter.
You'll also notice in the list of multimedia functions seven functions with the prefix mci that allow access to the Media Control Interface (MCI). This is a high-level, open-ended interface for controlling all multimedia hardware in the Multimedia PC. MCI includes many commands that are common to all multimedia hardware. This is possible because many aspects of multimedia can be molded into a tape recorder-like play/record metaphor. You "open" a device for either input or output, you "record" (for input) or "play" (for output), and when you're done you "close" the device.
MCI itself comes in two forms. In one form, you send messages to MCI that are similar to Windows messages. These messages include bit-encoded flags and C data structures. In the second form, you send text strings to MCI. This facility is primarily for scripting languages that have flexible string manipulation functions but not much support for calling Windows APIs. The string-based version of MCI is also good for interactively exploring and learning MCI, as we'll be doing shortly. Device names in MCI include cdaudio, waveaudio, sequencer (MIDI), videodisc, vcr, overlay (analog video in a window), dat (digital audio tape), and digitalvideo. MCI devices are categorized as "simple" and "compound." Simple devices (such as cdaudio) don't use files. Compound devices (like waveaudio) do; in the case of waveform audio, these files have a .WAV extension.
Another approach to accessing multimedia hardware involves the DirectX API, which is beyond the scope of this book.
Two other high-level multimedia functions also deserve mention: MessageBeep and PlaySound, which was demonstrated way back in Chapter 3. MessageBeep plays sounds that are specified in the Sounds applet of the Control Panel. PlaySound can play a .WAV file on disk, in memory, or loaded as resources. The PlaySound function will be used again later in this chapter.
Back in the early days of Windows multimedia, the software development kit included a C program called MCITEST that allowed programmers to interactively type in MCI commands and learn how they worked. This program, at least in its C version, has apparently disappeared. So, I've recreated it as the TESTMCI program shown in Figure 22-1. The user interface is based on the old MCITEST program but not the actual code, although I can't believe it was much different.
Figure 22-1. The TESTMCI program.
TESTMCI.C/*---------------------------------------- TESTMCI.C -- MCI Command String Tester (c) Charles Petzold, 1998 ----------------------------------------*/ #include <windows.h> #include "resource.h" #define ID_TIMER 1 BOOL CALLBACK DlgProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName [] = TEXT ("TestMci") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { if (-1 == DialogBox (hInstance, szAppName, NULL, DlgProc)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; } return 0 ; } BOOL CALLBACK DlgProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndEdit ; int iCharBeg, iCharEnd, iLineBeg, iLineEnd, iChar, iLine, iLength ; MCIERROR error ; RECT rect ; TCHAR szCommand [1024], szReturn [1024], szError [1024], szBuffer [32] ; switch (message) { case WM_INITDIALOG: // Center the window on screen GetWindowRect (hwnd, &rect) ; SetWindowPos (hwnd, NULL, (GetSystemMetrics (SM_CXSCREEN) - rect.right + rect.left) / 2, (GetSystemMetrics (SM_CYSCREEN) - rect.bottom + rect.top) / 2, 0, 0, SWP_NOZORDER | SWP_NOSIZE) ; hwndEdit = GetDlgItem (hwnd, IDC_MAIN_EDIT) ; SetFocus (hwndEdit) ; return FALSE ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDOK: // Find the line numbers corresponding to the selection SendMessage (hwndEdit, EM_GETSEL, (WPARAM) &iCharBeg, (LPARAM) &iCharEnd) ; iLineBeg = SendMessage (hwndEdit, EM_LINEFROMCHAR, iCharBeg, 0) ; iLineEnd = SendMessage (hwndEdit, EM_LINEFROMCHAR, iCharEnd, 0) ; // Loop through all the lines for (iLine = iLineBeg ; iLine <= iLineEnd ; iLine++) { // Get the line and terminate it; ignore if blank * (WORD *) szCommand = sizeof (szCommand) / sizeof (TCHAR) ; iLength = SendMessage (hwndEdit, EM_GETLINE, iLine, (LPARAM) szCommand) ; szCommand [iLength] = `\0' ; if (iLength == 0) continue ; // Send the MCI command error = mciSendString (szCommand, szReturn, sizeof (szReturn) / sizeof (TCHAR), hwnd) ; // Set the Return String field SetDlgItemText (hwnd, IDC_RETURN_STRING, szReturn) ; // Set the Error String field (even if no error) mciGetErrorString (error, szError, sizeof (szError) / sizeof (TCHAR)) ; SetDlgItemText (hwnd, IDC_ERROR_STRING, szError) ; } // Send the caret to the end of the last selected line iChar = SendMessage (hwndEdit, EM_LINEINDEX, iLineEnd, 0) ; iChar += SendMessage (hwndEdit, EM_LINELENGTH, iCharEnd, 0) ; SendMessage (hwndEdit, EM_SETSEL, iChar, iChar) ; // Insert a carriage return/line feed combination SendMessage (hwndEdit, EM_REPLACESEL, FALSE, (LPARAM) TEXT ("\r\n")) ; SetFocus (hwndEdit) ; return TRUE ; case IDCANCEL: EndDialog (hwnd, 0) ; return TRUE ; case IDC_MAIN_EDIT: if (HIWORD (wParam) == EN_ERRSPACE) { MessageBox (hwnd, TEXT ("Error control out of space."), szAppName, MB_OK | MB_ICONINFORMATION) ; return TRUE ; } break ; } break ; case MM_MCINOTIFY: EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_MESSAGE), TRUE) ; wsprintf (szBuffer, TEXT ("Device ID = %i"), lParam) ; SetDlgItemText (hwnd, IDC_NOTIFY_ID, szBuffer) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_ID), TRUE) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_SUCCESSFUL), wParam & MCI_NOTIFY_SUCCESSFUL) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_SUPERSEDED), wParam & MCI_NOTIFY_SUPERSEDED) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_ABORTED), wParam & MCI_NOTIFY_ABORTED) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_FAILURE), wParam & MCI_NOTIFY_FAILURE) ; SetTimer (hwnd, ID_TIMER, 5000, NULL) ; return TRUE ; case WM_TIMER: KillTimer (hwnd, ID_TIMER) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_MESSAGE), FALSE) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_ID), FALSE) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_SUCCESSFUL), FALSE) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_SUPERSEDED), FALSE) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_ABORTED), FALSE) ; EnableWindow (GetDlgItem (hwnd, IDC_NOTIFY_FAILURE), FALSE) ; return TRUE ; case WM_SYSCOMMAND: switch (LOWORD (wParam)) { case SC_CLOS E: EndDialog (hwnd, 0) ; return TRUE ; } break ; } return FALSE ; } |
TESTMCI.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Dialog TESTMCI DIALOG DISCARDABLE 0, 0, 270, 276 STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "MCI Tester" FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_MAIN_EDIT,8,8,254,100,ES_MULTILINE | ES_AUTOHSCROLL | WS_VSCROLL LTEXT "Return String:",IDC_STATIC,8,114,60,8 EDITTEXT IDC_RETURN_STRING,8,126,120,50,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_GROUP | NOT WS_TABSTOP LTEXT "Error String:",IDC_STATIC,142,114,60,8 EDITTEXT IDC_ERROR_STRING,142,126,120,50,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | NOT WS_TABSTOP GROUPBOX "MM_MCINOTIFY Message",IDC_STATIC,9,186,254,58 LTEXT "",IDC_NOTIFY_ID,26,198,100,8 LTEXT "MCI_NOTIFY_SUCCESSFUL",IDC_NOTIFY_SUCCESSFUL,26,212,100, 8,WS_DISABLED LTEXT "MCI_NOTIFY_SUPERSEDED",IDC_NOTIFY_SUPERSEDED,26,226,100, 8,WS_DISABLED LTEXT "MCI_NOTIFY_ABORTED",IDC_NOTIFY_ABORTED,144,212,100,8, WS_DISABLED LTEXT "MCI_NOTIFY_FAILURE",IDC_NOTIFY_FAILURE,144,226,100,8, WS_DISABLED DEFPUSHBUTTON "OK",IDOK,57,255,50,14 PUSHBUTTON "Close",IDCANCEL,162,255,50,14 END |
RESOURCE.H (excerpts)// Microsoft Developer Studio generated include file. // Used by TestMci.rc #define IDC_MAIN_EDIT 1000 #define IDC_NOTIFY_MESSAGE 1005 #define IDC_NOTIFY_ID 1006 #define IDC_NOTIFY_SUCCESSFUL 1007 #define IDC_NOTIFY_SUPERSEDED 1008 #define IDC_NOTIFY_ABORTED 1009 #define IDC_NOTIFY_FAILURE 1010 #define IDC_SIGNAL_MESSAGE 1011 #define IDC_SIGNAL_ID 1012 #define IDC_SIGNAL_PARAM 1013 #define IDC_RETURN_STRING 1014 #define IDC_ERROR_STRING 1015 #define IDC_DEVICES 1016 #define IDC_STATIC -1 |
Like many of the programs in this chapter, TESTMCI uses a modeless dialog box as its main window. Like all of the programs in this chapter, TESTMCI requires the WINMM.LIB import library to be listed in the Links page of the Projects Settings dialog box in Microsoft Visual C++.
This program uses the two most important multimedia functions. These are mciSendString and mciGetErrorText. When you type something into the main edit window in TESTMCI and press Enter (or the OK button), the program passes the string you typed in as the first argument to the mciSendString command:
error = mciSendString (szCommand, szReturn, sizeof (szReturn) / sizeof (TCHAR), hwnd) ;
If more than one line is selected in the edit window, the program sends them sequentially to the mciSendString function. The second argument is the address of a string that gets information back from the function. The program displays this information in the Return String section of the window. The error code returned from mciSendString is passed to the mciGetErrorString function to obtain a text error description; this is displayed in the Error String section of TESTMCI's window.
You can get an excellent feel for MCI command strings by taking control of the CD-ROM drive and playing an audio CD. This is a good place to begin because these command strings are often quite simple and, moreover, you get to listen to some music. You may want to have the MCI command string reference at /Platform SDK/Graphics and Multimedia Services/Multimedia Reference/Multimedia Command Strings handy for this exercise.
Make sure the audio output of your CD-ROM drive is connected to speakers or a headphone, and pop in an audio compact disc, for example, Bruce Springsteen's Born to Run. Under Windows 98, the CD Player application might start up and begin playing the album. If so, end the CD Player. Instead, bring up TESTMCI and type in the command
open cdaudio
and press Enter. The word open is an MCI command and the word cdaudio is a device name that MCI recognizes as the CD-ROM drive. (I'm assuming you have only one CD-ROM drive on your system; getting names of multiple CD-ROM drives requires use of the sysinfo command.)
The Return String area in TESTMCI shows the string that the system sends back to your program in the mciSendString function. If the open command works, this is simply the number 1. The Error String area in TESTMCI shows what the mciGetErrorString returns based on the return value from mciSendString. If mciSendString did not return an error code, the Error String area displays the text "The specified command was carried out."
Assuming the open command worked, you can now enter
play cdaudio
The CD will begin playing "Thunder Road," the first cut on the album. You can pause the CD by entering
pause cdaudio
or
stop cdaudio
For the cdaudio device, these statements do the same thing. You can resume playing with
play cdaudio
So far, all the strings we've used have been composed of a command and the device name. Some commands have options. For example, type
status cdaudio position
Depending how long you've been listening, the Return String area should show something like
01:15:25
What is this? It's obviously not hours, minutes, and seconds because the CD is not that long. To find out what the time format is, type
status cdaudio time format
The Return String area now shows the string
msf
This stands for "minutes-seconds-frames." In CD Audio, there are 75 frames to the second. The frame part of the time format can range from 0 through 74.
The status command has a bunch of options. You can determine the entire length of the CD in msf format using the command
status cdaudio length
For Born to Run, the Return String area will show
39:28:19
That's 39 minutes, 28 seconds, and 19 frames.
Now try
status cdaudio number of tracks
The Return String area will show
8
We know from the CD cover that the title tune is the fifth track on the Born to Run album. Track numbers in MCI commands begin at 1. We can find out how long the song "Born to Run" is by entering
status cdaudio length track 5
The Return String area shows
04:30:22
We can also determine where on the album this track begins
status cdaudio position track 5
The Return String area shows
17:36:35
With this information we can now skip directly to the title track:
play cdaudio from 17:36:35 to 22:06:57
This command will play the one song and then stop. That last value was calculated by adding 4:30:22 (the length of the track) to 17:36:35. Or, it could be determined by using
status cdaudio position track 6
Or, you can set the time format to tracks-minutes-seconds-frames:
set cdaudio time format tmsf
and then
play cdaudio from 5:0:0:0 to 6:0:0:0
or, more simply,
play cdaudio from 5 to 6
You can leave off trailing components of the time if they are 0. It is also possible to set the time format in milliseconds.
Every MCI command string can include the options wait or notify (or both) at the end of the string. For example, suppose you want to play only the first 10 seconds of the song "Born to Run," and right after that happens, you want the program to do something else. Here's one way to do it (assuming you've set the time format to tmsf):
play cdaudio from 5:0:0 to 5:0:10 wait
In this case, the mciSendString function does not return until the function has been completed, that is, until the 10 seconds of "Born to Run" have finished playing.
Now obviously, in general, this is not a good thing in a single-threaded application. If you accidentally typed
play cdaudio wait
the mciSendString function will not return control to the program until the entire album has played. If you must use the wait option (and it is handy when blindly running MCI scripts, as I'll demonstrate shortly), use the break command first. This command lets you set a virtual key code that will break the mciSendString command and return control to the program. For example, to set the Escape key to serve this purpose, use
break cdaudio on 27
where 27 is the decimal value of VK_ESCAPE. notify option:
play cdaudio from 5:0:0 to 5:0:10 notify
In this case, the mciSendString function returns immediately, but when the operation specified in the MCI command ends, the window whose handle is specified as the last argument to mciSendString receives an MM_MCINOTIFY message. The TESTMCI program displays the result of this message in the MM_MCINOTIFY group box. To avoid confusion as you may be typing in other commands, the TESTMCI program stops displaying the results of the MM_MCINOTIFY message after 5 seconds.
You can use the wait and notify keywords together, but there's hardly a reason for doing so. Without these keywords, the default behavior is to not wait and to not notify, which is usually what you want.
When you're finished playing around with these commands, you can stop the CD by entering
stop cdaudio
If you don't stop the CD-ROM device before closing it, the CD will continue to play even after you close the device.
You can try something that may or may not work with your hardware:
eject cdaudio
And then finally close the device like so:
close cdaudio
Although TESTMCI cannot save or load text files by itself, you can copy text between the edit control and the clipboard. You can select something in TESTMCI, copy it to the clipboard (using Ctrl-C), copy the text from the clipboard into NOTEPAD, and then save it. Reverse this process to load a series of MCI commands into TESTMCI. If you select a series of commands and press OK (or the Enter key), TESTMCI will execute the commands one at a time. This lets you construct MCI "scripts," which are simply lists of MCI commands.
For example, suppose you like to listen to the songs "Jungleland" (the last track on the album), "Thunder Road," and "Born to Run," in that order. Construct a script like so:
open cdaudio set cdaudio time format tmsf break cdaudio on 27 play cdaudio from 8 wait play cdaudio from 1 to 2 wait play cdaudio from 5 to 6 wait stop cdaudio eject cdaudio close cdaudio
Without the wait keywords, this wouldn't work correctly because the mciSendString commands would return immediately and the next one would then execute.
At this point, it should be fairly obvious how to construct a simple application that mimics a CD player. Your program can determine the number of tracks and the length of each track and can allow the user to begin playing at any point. (Keep in mind, however, that mciSendString always returns information in text strings, so you'll need to write parsing logic that converts those strings to numbers.) Such a program would almost certainly also use the Windows timer, for intervals of a second or so. During WM_TIMER messages, the program would call
status cdaudio mode
to see whether the CD is paused or playing. The
status cdaudio position
command lets the program update its display to show the user the current position. But something more interesting is also possible: if your program knows the time positions of key parts of the music, it can synchronize on-screen graphics with the CD. This is excellent for music instruction or for creating your own graphical music videos.