MMAPG - Media Control Interface
Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation
This section describes the services offered to applications by the media control interface for managing devices in the multimedia environment.
Command Message and Command String Interfaces
When a user activates a PM control to use a multimedia device function, the OS/2 multimedia application window procedure sends a command to the media control interface. Depending on the needs of the application, the window procedure can use the command message interface or the command string interface to implement these device commands. Messages for the command message interface (also referred to as procedural interface) are sent with mciSendCommand. Strings for the command string interface are sent to the Media Device Manager for parsing, using the mciSendString function. See the following figure.
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| mciSendCommand mciSendString |
| │ │ |
| │ │ |
| ▼ ▼ |
| ┌───────────────────────────────────────────────────────────┐ |
| │ Media Device Manager Interface Layer │ |
M └──┬───────────────────────┬────────────────────────────────┘ |
D │ │ |
M │ │ Default Tables |
| │ │ ┌──────────────┐|
| │ ┌────────────┴──────────────┐ ┌───┤ System │|
| │ │ Table-Driven Parser │-────────┤ ├──────────────┤|
| │ └────────────┬──────────────┘ │ │ waveaudio │|
| └─────────────┬─────────┘ | │ ├──────────────┤|
| │ │ │ │ sequencer │|
| | │ │ ├──────────────┤|
| ┌───────────────────────────┐ │ │ │ cdaudio │|
| │ Media Device Manager │ │ │ ├──────────────┤|
| └──┬────────┬────────┬──────┘ │ │ │ cdxa │|
|_ _ _ _│ _ _ _ _│ _ _ _ _│_ _ _ _ _ _ _ _ _│_ _ _ │ ├──────────────┤|
│ │ │ │ | │ │ ampmix │|
┌──┴───┐ ┌──┴───┐ ┌──┴───┐ │ | │ ├──────────────┤|
│Media │ │Media │ │Media │ │ | │ │ videodisc │|
│Driver│ │Driver│ │Driver│ │ | │ ├──────────────┤|
└──────┘ └──────┘ └──────┘ │ | │ │ digitalvideo │|
┌──────┐ │ | │ ├──────────────┤|
│Custom├─────────────┘ | └───┤ other │|
│Table │ | └──────────────┘|
└──────┘ | _ _ _ _ _ _ _ _ _ _ _ _ _|
The string interface provides access to most functions of the message interface. However, operations that involve identifying multiple devices (for example, for the purpose of establishing connections), or operations that return complex data structures (such as a CD table of contents) are available only through the message interface.
Each time a message is sent to the Media Device Manager with mciSendCommand, flags are set and a pointer to a data structure is passed. Each time a string is passed with mciSendString, it must be converted to the message format understood by the media driver. The Media Device Manager calls the multimedia string parser, which is case insensitive, to interpret the strings. The time required for this conversion process makes the string method of control slightly slower than the message method. However, the string interface generally requires less application code than the command message interface. The string interface also lets users interactively control devices with a command line or PM interface. See Command Strings.
Command Messages
Command messages are used by the command message interface and specified with mciSendCommand. Most command messages have corresponding string commands that are used by the command string interface and specified with mciSendString. Command messages are sent either to a logical device or to the system. The following table lists the command messages sent to devices. Commands that cause asynchronous responses to be generated, such as cue point and position advise, can be called using the appropriate string command; however, their responses are returned to window procedures.
| Command Messages Supported by All Devices | |
|---|---|
| MCI_OPEN | Establishes a specific instance of a multimedia device or file. |
| MCI_GETDEVCAPS | Gets the capabilities of a device. |
| MCI_INFO | Gets textual information from the device. |
| MCI_STATUS | Gets the current status of the device. |
| MCI_CLOSE | Closes the device. |
| Device Setup Command Messages | |
| MCI_SET | Changes the configuration of the device. |
| MCI_CONNECTOR | Enables, disables, or queries the state of a connector. |
| Playback and Recording Command Messages | |
| MCI_CUE | Prerolls a device for playing or recording. |
| MCI_SEEK | Seeks to a specified position in the file. |
| MCI_PLAY | Begins transmitting output data. |
| MCI_RECORD | Begins recording data from the specified position. |
| MCI_PAUSE | Suspends the playing or recording operation. |
| MCI_RESUME | Resumes the playing or recording operation. |
| MCI_STOP | Stops the playing or recording operation. |
| MCI_LOAD | Loads a data element into a media device. An example of a data element is a waveform file. |
| MCI_SAVE | Saves the current file to disk. |
| Synchronization Command Messages | |
| MCI_SET_CUEPOINT | Sets run-time cue points. |
| MCI_SET_POSITION_ADVISE | Advises the application when time elapses or position changes. |
| MCI_SET_SYNC_OFFSET | Biases MCI_PLAY starting positions and MCI_SEEK target positions for group operations. |
| Device-Specific Command Messages | |
| MCI_CAPTURE | Captures the current video image and stores it as an image device element. |
| MCI_ESCAPE | Sends a custom message directly to the media driver. |
| MCI_GETIMAGEBUFFER | Gets the contents of the capture video buffer or the current movie frame. |
| MCI_GETTOC | Gets a contents structure for the currently loaded CD-ROM disc. |
| MCI_PUT | Sets the source and destination rectangles for the transformation of the video image. It also sets the size and position of the default video. |
| MCI_REWIND | Seeks the media to the beginning point. |
| MCI_SETTUNER | Causes the digital video MCD to change the frequency the tuner device is tuned to. |
| MCI_SPIN | Spins the videodisc player up or down. |
| MCI_STEP | Advances or backs up the videodisc player one or more frames. |
| MCI_WHERE | Returns the source and destination rectangles set by MCI_PUT. It also returns the size and position of the video window. |
| MCI_WINDOW | Specifies the window in which to display video output, and controls the visibility of the default video window. |
| Editing Command Messages | |
| MCI_COPY | Copies specified data range into clipboard or buffer. |
| MCI_CUT | Removes specified data range and places it into clipboard or buffer. |
| MCI_DELETE | Deletes specified data range. Clipboard or buffer is not used. |
| MCI_PASTE | Deletes selected data range if difference between FROM and TO is more than zero, then inserts data from buffer or clipboard. |
| MCI_REDO | Reverses previous MCI_UNDO command. |
| MCI_UNDO | Cancels previous RECORD, CUT, PASTE, or DELETE. |
The following table lists the system command messages specified with mciSendCommand.
| MCI_DEVICESETTINGS | Provides a media control interface driver the opportunity to insert custom settings pages. |
| MCI_GROUP | Makes and breaks device group associations. |
| MCI_MASTERAUDIO | Sets the system master volume and toggles speakers and headphones. |
| MCI_SYSINFO | Gets and sets device and system information. |
| MCI_CONNECTORINFO | Gets information regarding the number and types of connectors defined for a device. |
| MCI_DEFAULT_CONNECTION | Makes, breaks, or queries default connections established for a device. |
| MCI_CONNECTION | Gets the device context connection or establishes an alias for a connected device. |
| MCI_ACQUIREDEVICE | Acquires a device for use. |
| MCI_RELEASEDEVICE | Releases a device from use. |
Command Strings
String commands utilize a more English text format than command messages. Following is the valid syntax for passing string commands directly to the media control interface:
<COMMAND> <DEVICE_TYPE|DEVICE_NAME|ALIAS|ELEMENT> <PARAMETERS>
This format is used for all string commands except masteraudio, which does not require a device name. The format for the masteraudio command is:
<COMMAND> <PARAMETERS>
An application calls mciSendString to pass the string command to the Media Device Manager for parsing and execution. The String Test Sample program, provided in the Toolkit (\TOOLKIT\SAMPLES\MM\MCISTRNG), illustrates the interpretive string interface. The following code fragment shows the call to mciSendString in the String Test Sample.
ulSendStringRC = mciSendString( (PSZ) &acMCIString[ 0 ], /* The MCI String Command */ (PSZ) &acMCIReturnString[ 0 ], /* Place for return strings */ (USHORT) MCI_RETURN_STRING_LENGTH, /* Length of return space */ hwndDisplayDialogBox, /* Window to receive notifies */ usCountOfMCIStringSent ); /* The user parameter */
The following is an example of the string commands required to open a CD player and play an entire CD.
open cdaudio01 alias cdaud1 shareable status cdaud1 media present wait status cdaud1 mode wait set cdaud1 time format milliseconds seek cdaud1 to start play cdaud1 notify . . . ** play the entire disc ** . . . close cdaud1
The status commands let the application know if a CD is present and if the drive is ready. Notice that wait flags are used; otherwise the commands would return immediately with no status information. The set command sets the time base to milliseconds for all future commands. The close command is sent after the application receives an MM_MCINOTIFY message at the completion of the play command.
- Note
- The close command can be sent at any time.
Authoring languages that include support for the media control interface can integrate device command strings like these with authoring language syntax to create multimedia presentations. The string interface provides a 16-bit interface to enable developers to integrate multimedia function with the macro languages of existing 16-bit applications.
Wait and Notify Flags
An application can set a wait or a notify flag on a device command sent with mciSendString or mciSendCommand. These two flags are mutually exclusive and are available on all commands except some system commands.
| Flag | Description |
|---|---|
| wait | The command is executed synchronously. The function waits until the requested action is complete before returning to the application. |
| notify | The command is executed asynchronously, allowing control to be returned immediately to the application. When the requested action is complete, an MM_MCINOTIFY message is sent to the application window procedure. |
- Note
- If a command is issued without a wait flag or notify flag specified, the command is executed asynchronously, and the application is never notified.
The wait flag is useful for operations that are conducted quickly, like the playback of short sounds, which the application wants to complete before it continues. The wait flag is also useful for operations that return information, such as device capabilities, because the Media Device Manager parser converts the return code to a meaningful string. However, the conversion occurs only if the wait flag is specified.
The wait flag should be used with care when issuing commands from threads that read application input message queues as it ties up the thread, preventing all PM messages in the system from being processed while the command issued with the wait flag is executed.
The notify flag is useful for operations that are conducted over a period of time. For example, the playing of a waveform file often can take a while to complete. By specifying the notify flag, an application requests to be notified when processing of the command is complete. The application window procedure can then remain responsive to input queue processing.
OS/2 Multimedia Notification Messages
The system returns notification messages to applications to indicate OS/2 multimedia events such as completing a media device function or passing ownership of a media device from one process to another. Following is a list of OS/2 multimedia notification messages.
| Notification Message | Reason for Notification |
|---|---|
| MM_MCICUEPOINT | A cue point was detected. Cue points are set with the playlist CUEPOINT instruction or the MCI_SET_CUEPOINT command message. |
| MM_MCIEVENT | A device has generated an event. |
| MM_MCINOTIFY | A device has completed an action, or an error has occurred. |
| MM_MCIPASSDEVICE | A shared device is being lost or gained. |
| MM_MCIPLAYLISTMESSAGE | A MESSAGE instruction was encountered in a playlist. |
| MM_MCIPOSITIONCHANGE | The time period or position specified with the MCI_SET_POSITION_ADVISE command message has passed. |
With the exception of MM_MCIEVENT, the system returns notification messages asynchronously to applications using WinPostMsg; MM_MCIEVENT notifications are returned synchronously with WinSendMsg.
A PM application receives notifications by passing its message queue window handle as a parameter on the mciSendCommand or mciSendString call. Applications can also receive notifications by passing a handle to a Control Program queue. For more information see Using a Control Program Queue for Notifications.
If an application sends a command message with mciSendCommand and specifies the MCI_NOTIFY flag, control returns immediately to the application. The media control interface posts a notification message to the window specified in the callback window handle after the command completes processing. The MM_MCINOTIFY message is returned asynchronously to the application using WinPostMsg. It can have any of the following values:
| Notification Code | Meaning |
|---|---|
| MCI_NOTIFY_SUCCESSFUL | The command completed successfully. |
| MCI_NOTIFY_SUPERSEDED | Another command is being processed. |
| MCI_NOTIFY_ABORTED | Another command interrupted this one. |
- Note
- If none of the above notification codes are returned, an error code is returned, indicating that the asynchronous processing of the command ended in an error condition. To convert the error code to a textual description of the error, the application calls the mciGetErrorString function.
The following code fragment illustrates how the Audio Recorder Sample program, provided in the Toolkit (\TOOLKIT\SAMPLES\MM\RECORDER), handles the MM_MCINOTIFY notification message.
case MM_MCINOTIFY:
/*
* This message is returned to an application when a device
* successfully completes a command that was issued with a NOTIFY
* flag, or when an error occurs with the command.
*
* This message returns two values. A user parameter (mp1) and
* the command message (mp2) that was issued. The low word of mp1
* is the Notification Message Code, which indicates the status of the
* command like success or failure. The high word of mp2 is the
* Command Message which indicates the source of the command.
*/
usNotifyCode = (USHORT) SHORT1FROMMP( mp1); /* low-word */
usCommandMessage = (USHORT) SHORT2FROMMP( mp2); /* high-word */
switch (usCommandMessage)
{
case MCI_PLAY:
switch (usNotifyCode)
{
case MCI_NOTIFY_SUCCESSFUL:
if (eState != ST_STOPPED)
{
/*
* Update the status line with appropriate message.
*/
UpdateTheStatusLine(hwnd, IDS_STOPPED);
eState = ST_STOPPED;
/*
* Stop the play button animation
*/
WinSendMsg( hwndPlayPB, /* Play button handle */
GBM_ANIMATE, /* Animation control */
MPFROMSHORT(FALSE),/* Animation flag */
NULL ); /* Ignore return data */
}
break;
case MCI_NOTIFY_SUPERSEDED:
case MCI_NOTIFY_ABORTED:
/* we don't need to handle these messages. */
break;
default:
/*
* If the message is none of the above, then it must be
* a notification error message.
*/
ShowMCIErrorMessage( usNotifyCode);
eState = ST_STOPPED;
/*
* Stop the play button animation and update the status
* line with appropriate text.
*/
WinSendMsg( hwndPlayPB, /* Play button handle */
GBM_ANIMATE, /* Animation control */
MPFROMSHORT(FALSE), /* Animation flag */
NULL ); /* Ignore return data */
UpdateTheStatusLine(hwnd, IDS_STOPPED);
break;
}
break;
}
return( (MRESULT) 0);
Using a Control Program Queue for Notifications
An OS/2 application that does not have a PM window, and therefore cannot use a PM message queue for receiving notification messages, can use an OS/2 Control Program queue instead. The Control Program queue method of notification should also be considered for time-critical PM applications, because it is faster than using the PM message queue.
To receive notifications on a Control Program queue, the application must specify the MCI_DOS_QUEUE flag with the MCI_OPEN command message when using mciSendCommand (or the dosqueue keyword with the open command when using mciSendString). This flag indicates that the window handle specified for receiving notification messages is actually a handle to a Control Program queue, not a PM window.
The application issues DosReadQueue, which mimics the PM message queue function by blocking until there is something in the queue to process. To place a notification message in the queue, the system issues DosWriteQueue and specifies the queue handle given to it by the application.
The syntax for Control Program queues and PM message queues varies slightly. A typical PM message queue process function has the following four parameters:
HWND hwnd /* Window handle */ ULONG msg /* Notification type */ MPARAM mp1 /* Message parameter 1 */ MPARAM mp2 /* Message parameter 2 */
The DosReadQueue call has eight parameters. The system uses the first four parameters of DosReadQueue in the same manner as a PM message queue process function.
HQUEUE hq /* Queue handle */ PREQUESTDATA pRD /* Pointer to REQUESTDATA */ PULONG pmp1 /* Pointer to mp1 information */ PPVOID pmp2 /* Pointer to mp2 information */
The second parameter of DosReadQueue points to the REQUESTDATA structure, which contains a ulData field. This field corresponds to the msg field of PM message queues. The application can use the remaining parameters of DosReadQueue for its own purposes.
The following example code illustrates how an application can use Control Program queue functions to handle multimedia notification messages.
{
PID OwnerPID=0;
HQUEUE QHandle =0;
char ch, retstring[100], QueueName[100];
strcpy(QueueName, "\\QUEUES\\");
if (argc == 2) strcat(QueueName, argv[1]);
else strcat(QueueName, "DEFAULT");
if (DosCreateQueue(&QHandle, QUE_FIFO, QueueName)) return(1L);
if (DosOpenQueue(&OwnerPID, &QHandle, QueueName)) return(1L);
_beginthread( QMonitor, (PVOID)NULL, 65536L, (PVOID)QHandle);
SendString((LPSTR)"open d:\\mmos2\\sounds\\applause.wav shareable
dosqueue wait alias a",
(LPSTR)retstring, 100,
(HWND)QHandle, 0);
SendString((LPSTR)"setpositionadvise a on every 3000 wait",
(LPSTR)retstring, 100,
(HWND)QHandle, 0);
SendString((LPSTR)"play a notify", (LPSTR)retstring, 100, (HWND)QHandle, 0);
ch=(char)getch();
SendString((LPSTR)"close a wait", (LPSTR)retstring, 100, (HWND)QHandle, 0);
DosCloseQueue(QHandle);
return(0L);
}
VOID _Optlink QMonitor( PVOID qh)
{
APIRET rc=0;
BYTE Priority;
REQUESTDATA RD;
ULONG msg, mp1, mp2;
printf("QMonitor Started!\n");
while (1)
{
DosReadQueue((HQUEUE)qh, &RD, (PULONG)&mp1, (PPVOID)&mp2, 0L, (BOOL32)0,
&Priority, (HEV)0);
switch(RD.ulData)
{
case MM_MCINOTIFY:
printf(" msg=MM_MCINOTIFY:\n");
printf(" mp1=%d\n",mp1);
printf(" mp2=%d\n",mp2);
break;
case MM_MCIPASSDEVICE:
printf(" msg=MM_MCIPASSDEVICE:\n");
printf(" Device id=%d\n",mp1);
switch (mp2)
{
case MCI_LOSING_USE:
printf(" MCI_LOSING_USE:\n");
break;
case MCI_GAINING_USE:
printf(" MCI_GAINING_USE:\n");
break;
default:
printf(" mp2=%d\n",mp2);
break;
}
break;
case MM_MCIPOSITIONCHANGE:
printf(" msg=MM_MCIPOSITIONCHANGE:\n");
printf(" mp1=%d\n",mp1);
printf(" mp2=%d\n",mp2);
break;
case MM_MCICUEPOINT:
printf(" msg=MM_MCICUEPOINT:\n");
printf(" mp1=%d\n",mp1);
printf(" mp2=%d\n",mp2);
break;
case MM_MCIPLAYLISTMESSAGE:
printf(" msg=MM_MCIPLAYLISTMESSAGE:\n");
printf(" mp1=%d\n",mp1);
printf(" mp2=%d\n",mp2);
break;
case MM_MCIEVENT:
printf(" msg=MM_MCIEVENT:\n");
printf(" mp1=%d\n",mp1);
printf(" mp2=%d\n",mp2);
break;
default:
printf(" msg=%d\n",RD.ulData);
printf(" mp1=%d\n",mp1);
printf(" mp2=%d\n",mp2);
break;
}
} /* endwhile */
return;
}
VOID SendString( LPSTR string, LPSTR retstring, ULONG retsize, HWND Handle,
ULONG userparm)
{
ULONG rc;
rc=mciSendString(string, retstring, retsize, Handle, userparm);
if (rc) printf("Error: (%s) rc=%d\n",string,rc);
return;
}
/* QUEUE.H */
#define INCL_BASE
#define INCL_32
#include<os2.h>
#include<os2me.h>
#include<mmioos2.h>
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
int main(int argc, char *argv[], char *envp[]);
VOID SendString(LPSTR string,
LPSTR retstring,
ULONG retsize,
HWND Handle,
ULONG userparm);
VOID _Optlink QMonitor( PVOID qh);
Time Formats for Device Commands
Media position and time information are required as input and also returned as output by many multimedia commands. Time formats vary, depending on the device being used and the format of the data being operated on. The default time base for both the procedural and string interfaces is MMTIME. See MMTIME Format.
Other time formats, such as milliseconds, are also supported.
Time formats used by media control interface devices for measuring time are listed in the following table. The flags shown in the table are set with the MCI_SET command.
| Device | Formats | Flags |
|---|---|---|
| CD-DA | milliseconds mmtime minutes-seconds-frames tracks-min-sec-frame |
MCI_FORMAT_MILLISECONDS MCI_FORMAT_MMTIME MCI_FORMAT_MSF MCI_FORMAT_TMSF |
| CD-XA | milliseconds mmtime |
MCI_FORMAT_MILLISECONDS MCI_FORMAT_MMTIME |
| digital video | milliseconds mmtime frames hours-minutes-seconds hours-min-sec-frames |
MCI_FORMAT_MILLISECONDS MCI_FORMAT_MMTIME MCI_FORMAT_FRAMES MCI_FORMAT_HMS MCI_FORMAT_HMSF |
| waveform audio | milliseconds mmtime bytes samples |
MCI_FORMAT_MILLISECONDS MCI_FORMAT_MMTIME MCI_FORMAT_BYTES MCI_FORMAT_SAMPLES |
| MIDI sequencer | milliseconds mmtime SMPTE 24 SMPTE 25 SMPTE 30 SMPTE 30 song pointer |
MCI_FORMAT_MILLISECONDS MCI_FORMAT_MMTIME MCI_SEQ_SET_SMPTE_24 MCI_SEQ_SET_SMPTE_25 MCI_SEQ_SET_SMPTE_30 MCI_SEQ_SET_SMPTE_30DROP MCI_SEQ_SET_SONGPTR |
MMTIME Format
MMTIME is a standard time and media position format supported by the media control interface. This time unit is 1/3000 second, or 333 microseconds. Conversion macros are provided for convenient conversion of other popular time formats to and from this format. MMTIME values are passed as long (32-bit) integer values.
To use MMTIME on command messages, send the MCI_SET message specifying the MCI_SET_TIME_FORMAT flag. Use MCI_FORMAT_MMTIME in the ulTimeFormat field of the MCI_SET_PARMS structure.
The macros shown in the following figure are available for conversion to and from the MMTIME format.
| Conversion to MMTIME | Conversion to Other Formats |
|---|---|
| REDBOOKTOMM (ULONG) | REDBOOKFROMMM (ULONG) |
| FPS24TOMM (ULONG) | FPS24FROMMM (ULONG) |
| FPS25TOMM (ULONG) | FPS25FROMMM (ULONG) |
| FPS30TOMM (ULONG) | FPS30FROMMM (ULONG) |
| MSECTOMM (ULONG) | MSECFROMMM (ULONG) |
| HMSTOMM (ULONG) | HMSFROMMM (ULONG) |
- Packed Time Formats
The packed time formats described in the following sections require that the application format the ULONG value passed in command message parameter structures. When these values are passed in string commands, any value containing a colon (:) is assumed to be a field-oriented value. For example, if the time format for a CD audio device is set to TMSF, and the value 4:10:00:00 is specified, this value is interpreted as track 4, 10 minutes, 0 seconds, and 0 frames. However, if the value 4100000 is specified, the integer is passed directly, and the assignment to byte fields is quite different.
It is not required that a field-oriented value contain specifications for all fields. For example, the following are equivalent specifications for track 4:
4:00:00:00 4:00:00 4:00: 4:00 4: 4
The interpretation of field-oriented values is left-justified with respect to the placement of colons. Values not specified default to zero. If a value has a colon, it is subject to field-oriented interpretation, regardless of the time format currently set for the device.
- HMSF (SMPTE) Packed Time Format
The HMSF packed time format represents elapsed hours, minutes, seconds, and frames from any specified point. This time format is packed into a 32-bit ULONG value as follows:
| High-Order Byte | Low-Order Byte | High-Order Byte | Low-Order Byte |
|---|---|---|---|
| Frames | Seconds | Minutes | Hours |
- MSF Packed Time Format
The CD-DA MSF time format, also referred to as the Red Book time format, is based on the 75-frame-per-second CD digital audio standard. Media position values in this format are packed into a 32-bit ULONG value as follows:
| High-Order Byte | Low-Order Byte | High-Order Byte | Low-Order Byte |
|---|---|---|---|
| Reserved | Frames | Seconds | Minutes |
The following macros aid in extracting information in packed MSF format:
Macro
Description MSF_MINUTE(time)
Gets the number of minutes. MSF_SECOND(time)
Gets the number of seconds. MSF_FRAME(time)
Gets the number of frames.
For example, the following code fragment sets the time in ulTime to 6 minutes and 30 seconds (06:30:00).
ULONG ulTime;.
.
.
MSF_MINUTE(ulTime) = 6
MSF_SECOND(ulTime) = 30;
MSF_FRAME(ulTime) = 0;
TMSF Packed Time Format
The CD-DA TMSF time format is based on the 75-frame-per-second CD digital audio standard. Media position values in this format are packed into a 32-bit ULONG value as follows:
| High-Order Byte | Low-Order Byte | High-Order Byte | Low-Order Byte |
|---|---|---|---|
| Frames | Seconds | Minutes | Track |
The following macros aid in extracting information in packed TMSF format:
Macro
Description TMSF_TRACK(time)
Gets the number of tracks. TMSF_MINUTE(time)
Gets the number of minutes. TMSF_SECOND(time)
Gets the number of seconds. TMSF_FRAME(time)
Gets the number of frames.
For example, the following code fragment sets the time in ulTime to 2 minutes into track 2 (02:02:00:00).
ULONG ulTime;. . . TMSF_TRACK(ulTime) = 2; TMSF_MINUTE(ulTime) = 2; TMSF_SECOND(ulTime) = 0; TMSF_FRAME(ulTime) = 0;
- Note
- MSF and TMSF macros can be found in the MCIOS2.H file.
- HMS Packed Time Format
The HMS packed time format, representing hours, minutes, and seconds, is packed into a 32-bit ULONG value as follows:
| High-Order Byte | Low-Order Byte | High-Order Byte | Low-Order Byte |
|---|---|---|---|
| Reserved | Seconds | Minutes | Hours |
Opening a Media Device
Media devices are categorized as simple or compound devices. A compound device is an internal device that operates on data objects, such as files, within the system. These data objects are referred to as device elements. A simple device is an external device that does not require a device element.
Media device types supported by OS/2 multimedia are shown in the following table.
| Logical Device Type | String | Constant |
|---|---|---|
| Amplifier-mixer | ampmix | MCI_DEVTYPE_AUDIO_AMPMIX |
| CD-DA player | cdaudio | MCI_DEVTYPE_CD_AUDIO |
| CD-XA player | cdxa | MCI_DEVTYPE_CDXA_PLAYER |
| Digital video player | digitalvideo | MCI_DEVTYPE_DIGITAL_VIDEO |
| MIDI sequencer | sequencer | MCI_DEVTYPE_SEQUENCER |
| Waveform audio player | waveaudio | MCI_DEVTYPE_WAVEFORM_AUDIO |
| Videodisc player | videodisc | MCI_DEVTYPE_VIDEODISC |
Device type constants represent one way of specifying devices in command messages. String names can be specified in either command messages or command strings.
To use the string interface to communicate with a device, an application calls mciSendString and passes the textual command open. Following is the syntax used for the textual command:
open device_name <shareable> <type device_type > <alias alias>
Parameters for the open command are:
| Parameters | Description |
|---|---|
| device_name | Specifies the name of a device or device element. |
| shareable | Indicates the device or device element may be shared by other applications. |
| type device_type | Specifies the device type when device_name is a device element. |
| alias alias | Specifies an alternate name for the device. |
Here is an example of the syntax for opening a device:
open horns.wav type waveaudio alias sound1
where "horns.wav" is the device element and "waveaudio" is the device type.
The system also supports a shortcut version of the syntax: open device_type!element_name
The shortcut version of the previous example looks like this:
open waveaudio!horns.wav alias sound1
File Type Associations
A specific device can have file extensions and .TYPE EAs (Extended Attributes) associated with it. The OS/2 multimedia user can map a file extension or .TYPE EA to a specific device with the Multimedia Setup application located in the Multimedia folder. An OS/2 multimedia subsystem developer writing an installation DLL can map a file extension or .TYPE EA to a device using MCI_SYSINFO_SET_EXTENSIONS or MCI_SYSINFO_SET_TYPES. For an extension or .TYPE EA to be mapped to a device, it must be unique across installation names.
For example, the Multimedia Setup application can be used to associate the WAV extension with the waveaudio01 device. The device can then be opened by passing the name of a data element with a WAV extension as a parameter in the open command to mciSendString. Suppose the following string is passed:
open honk.wav wait
The waveaudio01 device is opened with the data file honk.wav.
Default and Specific Devices
The following table shows some examples of open commands. A default device is opened if only a logical device type (for example, waveaudio) is specified in the open command. The default device for a logical device type can be queried and set by the user with the Multimedia Setup application. The default device also can be queried and set with MCI_SYSINFO by an installation DLL for a media device.
A specific device is opened by specifying its name (for example waveaudio01), or by specifying a device element with an extension or .TYPE EA that is associated with the device.
| Open Command | Description |
|---|---|
| open waveaudio | Opens a default device of type waveaudio. |
| open waveaudio01 | Opens a specific device of type waveaudio. |
| open foo.xyz | Opens a specific device that is associated with the .TYPE EA (if any) of foo.xyz; otherwise opens a specific device that has a unique association with file extension xyz; otherwise returns MCIERR_INVALID_DEVICE_NAME. |
By setting the shareable flag for an open request, an application can share an OS/2 multimedia device with other applications. To enable device sharing, the multimedia system posts the MM_MCIPASSDEVICE message with WinPostMsg to the application. The message informs the application the device context is becoming active (MCI_GAINING_USE) or inactive (MCI_LOSING_USE).
After the application processes the MCI_GAINING_USE event notification, it can issue device commands. The device context becomes inactive when the MCI_LOSING_USE event notification is received.
If the application has specified a notify flag on the open, the receipt of an MM_MCINOTIFY message does not mean the device context is active. When MCI_NOTIFY_SUCCESSFUL is received, the commands status, capability, and info can be issued, because the multimedia system allows these commands to be made to inactive instances. If the application issues a command to an inactive instance and the instance must be active to process the command, the multimedia system returns MCIERR_INSTANCE_INACTIVE.
When an application opens a device without setting the shareable flag, the Media Device Manager attempts to acquire the device for the exclusive use of the application. If a device context already exists that was either opened as nonshareable by another application or opened as shareable but then acquired exclusively by another application, the open fails and the application receives the MCIERR_DEVICE_LOCKED error code. The application can subsequently make the device context shareable by issuing an MCI_RELEASEDEVICE message.
See Device Sharing By Applications for more detailed information on device sharing.
Device Alias
When a device is opened, it can be given an alias, or alternate name. The primary use of a device alias is to simplify the specifying of subsequent commands to control the device through the string interface. A device alias is referenced only from the string interface, and it is valid only within the process that opened the device context.
For example, the following strings can be passed with mciSendString:
open horns.wav alias honk play honk
A secondary use of the device alias is to differentiate between device contexts opened by the same process. For example:
open horns.wav alias honk open bells.wav alias ring play ring wait play honk wait
Note: The maximum length for an alias is 20 characters. Placing an alias in quotation marks is permitted.
When a device is opened using the string interface, a device context ID is returned. If the application provides a return buffer in the call to mciSendString, the ID can be used to issue commands to the device context using the mciSendCommand interface, when necessary.
Using the Command Message Interface
To use the command message interface to communicate with a device, an application calls mciSendCommand and passes the command message MCI_OPEN. If the request is successful, a device handle for access to the device context is returned in the usDeviceID field of the MCI_OPEN_PARMS data structure. This handle is retained for use in subsequent message commands.
An alias can be specified with the MCI_OPEN_ALIAS flag in the command message MCI_OPEN. Commands can then be issued to the device context by means of the string interface.
The following code fragment shows the opening of devices in the Duet Player I sample program. The hwndCallback field contains the application's main window procedure so that the MM_MCIPASSDEVICE messages are sent to it when the duet player gains or passes control of the device. The device ID and type fields of the structure are not needed because the audio file name is specified as the element field of the structure. This causes the Media Device Manager (MDM) to open the appropriate device based on the file name extension. Once the MCI_OPEN_PARMS structure is initialized, an MCI_OPEN command is specified with the mciSendCommand function for each separate part of the duet.
/*
* Open one part of the duet. The first step is to initialize an
* MCI_OPEN_PARMS data structure with the appropriate information,
* then issue the MCI_OPEN command with the mciSendCommand function.
* We will be using an open with only the element name specified.
* This will cause the default connection, as specified in the
* MMPM.INI file, for the data type.
*/
mopDuetPart.hwndCallback = (ULONG) hwnd; /* For MM_MCIPASSDEVICE */
mopDuetPart.usDeviceID = (USHORT) NULL; /* this is returned */
mopDuetPart.pszDeviceType = (PSZ) NULL; /* using default conn. */
mopDuetPart.pszElementName = (PSZ) aDuet[sDuet].achPart1;
ulError = mciSendCommand( (USHORT) 0,
MCI_OPEN,
MCI_WAIT | MCI_OPEN_ELEMENT |
MCI_OPEN_SHAREABLE | MCI_READONLY,
(PVOID) &mopDuetPart,
UP_OPEN);
if (!ulError) /* if we opened part 1 */
{
usDuetPart1ID = mopDuetPart.usDeviceID;
/*
* Now, open the other part
*/
mopDuetPart.pszElementName = (PSZ) aDuet[sDuet]achPart2;
ulError = mciSendCommand( (USHORT) 0,
MCI_OPEN,
MCI_WAIT | MCI_OPEN_ELEMENT |
MCI_OPEN_SHAREABLE | MCI_READONLY,
(PVOID) &mopDuetPart,
UP_OPEN);
if (!ulError) /* if we opened part 2 */
{
usDuetPart2ID = mopDuetPart.usDeviceID;
}
}
Memory Playlists
In addition to specifying files or Resource Interchange File Format (RIFF) chunks to be loaded by compound devices, you also can specify memory objects. You create memory objects, for example, to play synthesized audio using the waveform audio media driver. These memory objects can be placed under the control of the memory playlist.
The memory playlist is a data structure in an application. It contains an array of simple, machine-like instructions you formulate, each of which has a fixed format consisting of a 32-bit operation code and three 32-bit operands. Playlist instructions are described in the following table.
To have playlist instructions interpreted by the playlist processor, you specify the MCI_OPEN_PLAYLIST flag with the MCI_OPEN command message. This flag indicates that the pszElementName field in the MCI_OPEN_PARMS data structure is a pointer to a memory playlist.
Using playlist instructions, you can play audio objects in succession from one or more memory buffers. Instructions include branching to and returning from subroutines within the playlist. In addition, the playlist can be modified dynamically by the application while it is being played. Because less overhead is involved when playing audio data from memory, playlist programs will have higher performance. If your application requires speed or if it needs to modify the data before it is sent to the audio device, use playlists.
| Command | Description |
|---|---|
| BRANCH_OPERATION | Transfers control to another instruction in the playlist.
Branching out of a subroutine is not prohibited; however, it is not recommended because an unused return address is left on the stack maintained by the playlist processor. An application can enable or disable a BRANCH_OPERATION by exchanging it with a NOP_OPERATION. Operands for a NOP_OPERATION are ignored. |
| CALL_OPERATION | Transfers control to the absolute instruction number specified in Operand 2, saving the number of the instruction following the CALL for use on a RETURN instruction.
CALL instructions may be nested up to 20 levels.
|
| CUEPOINT_OPERATION | Causes a cue point data record to be entered into the data stream. Note that the cue point is relative to the DATA_OPERATION that follows it.
The MM_MCICUEPOINT message is returned to the application as soon as possible after the cue point data record is encountered in the data stream. The message is sent to the window handle specified when the device was originally opened. Note: The CUEPOINT instruction is ignored when used in a recording operation. |
| DATA_OPERATION | Specifies a data buffer to be played from or recorded into.
The current position in the buffer is particularly important after a recording operation, because this field contains the number of bytes of recorded data. The remaining bytes in the buffer are not valid. This field is initialized to zero when the DATA_OPERATION statement is first encountered. The buffer indicated by the DATA instruction must only contain the raw data bytes from the device and cannot include any header information. Therefore, the precise meaning or format of the data is dependent on the current settings of the media device. For example, a wave audio data element is assumed to have the format PCM or ADPCM, number of bits per sample, and so on, that is indicated by the settings of the audio device. |
| EXIT_OPERATION | Indicates the end of the playlist.
|
| LOOP_OPERATION | Controls iteration in a playlist. It is the responsibility of the application to initialize the current iteration. The current iteration is reset to zero following loop termination.
The last instruction in a loop is a branch back to the LOOP_OPERATION. The operation of the LOOP_OPERATION instruction is as follows:
Typically, the application sets the iteration count to zero when the playlist is passed to the device, but this is not required. The loop instruction merely compares the loop count with the iteration count. If the iteration count is set to a value other than zero when the playlist is passed in, it is as if the loop has been executed that number of times. Also, if a playback operation is stopped, and then the same playlist is loaded again, the loop iteration count is not initialized by the playlist processor. It is the application's responsibility to see that iteration count values are what is required when switching from play to record, record to play, and when changing settings for the data (for example, bitspersample, samplespersec, and so on) with the set command. These commands cause the playlist stream to be destroyed and re-created, and the playlist to be reassociated as a new playlist with the playlist processor. |
| MESSAGE_OPERATION | Returns a message to the application during playlist processing.
Each time the playlist processor encounters a MESSAGE instruction, MM_MCIPLAYLISTMESSAGE is returned to the application. MESSAGE instructions can be used by the application to trace specific points during the execution of the playlist processor. The message is sent to the window handle specified when the device was originally opened. This function is not intended to be used for timing of data production or consumption identified by previously interpreted instructions. Do not rely on the MESSAGE instruction to indicate precisely when a particular piece of digital audio has been played by an audio device; however, the MESSAGE instruction can be used to indicate when a buffer has been consumed and needs to be refilled. |
| NOP_OPERATION | Used as a placeholder.
|
| RETURN_OPERATION | Transfers control to the playlist instruction following the most recently executed CALL instruction.
|
| SEMPOST_OPERATION | Causes the playlist processor to post an event semaphore. The playlist processor will call DosWaitEventSem.
|
| SEMWAIT_OPERATION | Causes the playlist processor to wait on a semaphore. The playlist processor will call DosWaitEventSem.
|
Clock Sample Program Playlist Example
The data structure in the following figure holds the playlist that is used to play the chimes in the Clock Sample program provided in the Toolkit (\TOOLKIT\SAMPLES\MM\CLOCK). Note that the definitions for the playlist operation codes can be found in the MCIOS2.H file.
/*
* This double array holds the playlists that will be used to play the
* chimes for the clock. Each array has three fields within the
* structure: one for the playlist command (32-bit value) and three
* operands (32-bit values). The DATA_OPERATION's first operand will
* contain the address to the respective waveform buffers. Once the
* playlist has been played, the CHIME_PLAYING_HAS_STOPPED message
* will be sent so that the application knows that the audio has
* finished.
* The clock will have a unique chime for each quarter hour.
* There are three chime files that are used in different combinations
* to create all of the chimes used for the clock. These three files
* are CLOCK1.WAV, CLOCK2.WAV, and CLOCK3.WAV.
*
* The first playlist will play quarter hour chime. This is simply
* CLOCK1.WAV.
*
* The second playlist will play the half hour chime. This
* consists of CLOCK1.WAV + CLOCK2.WAV.
*
* The third playlist will play the three quarter hour chime. This
* consists of CLOCK1.WAV + CLOCK2.WAV + CLOCK1.WAV.
*
* The fourth playlist plays the hour chime. This consists of
* CLOCK1.WAV + CLOCK2.WAV + CLOCK1.WAV + CLOCK2.WAV +
* (HOUR * CLOCK3.WAV)
* The Number of loops to perform for the hour value is kept in
* the first operand. This will be set in a later procedure when the
* hour of the chime time is known.
*/
PLAY_LIST_STRUCTURE_T apltPlayList[ NUMBER_OF_PLAYLISTS ]
[ NUMBER_OF_COMMANDS ] =
{
/*
* Quarter Hour Chime.
*/
{
DATA_OPERATION, 0, 0, 0, /* Chime file 1. */
MESSAGE_OPERATION, 0, CHIME_PLAYING_HAS_STOPPED, 0,
EXIT_OPERATION, 0, 0, 0
},
/*
* Half Hour Chime.
*/
{
DATA_OPERATION, 0, 0, 0, /* Chime file 1. */
DATA_OPERATION, 0, 0, 0, /* Chime file 2. */
MESSAGE_OPERATION, 0, CHIME_PLAYING_HAS_STOPPED, 0,
EXIT_OPERATION, 0, 0, 0
},
/*
* Three Quarter Hour Chime.
*/
{
DATA_OPERATION, 0, 0, 0, /* Chime file 1. */
DATA_OPERATION, 0, 0, 0, /* Chime file 2. */
DATA_OPERATION, 0, 0, 0, /* Chime file 1. */
MESSAGE_OPERATION, 0, CHIME_PLAYING_HAS_STOPPED, 0,
EXIT_OPERATION, 0, 0, 0
},
/*
* Hour Chime.
*/
{
DATA_OPERATION, 0, 0, 0, /* Chime file 1. < Line 0 >*/
DATA_OPERATION, 0, 0, 0, /* Chime file 2. < Line 1 >*/
DATA_OPERATION, 0, 0, 0, /* Chime file 1. < Line 2 >*/
DATA_OPERATION, 0, 0, 0, /* Chime file 2. < Line 3 >*/
DATA_OPERATION, 0, 0, 0, /* Chime file 3. < Line 4 >*/
LOOP_OPERATION, 0, 4, 0, /* Which line to loop on. < Line 5 >*/
MESSAGE_OPERATION, 0, CHIME_PLAYING_HAS_STOPPED, 0,
EXIT_OPERATION, 0, 0, 0
}
To prevent lost data, the address range of memory buffers used in DATA operations should not overlap.
Setting up the Playlist
Playlists operate on data from memory. Therefore, space must be allocated for the memory that will be utilized with the playlist.
The SetupPlaylist procedure is performed once, during initialization of the Clock Sample program. It calls the procedure CopyWaveformIntoMemory to copy the waveform files into memory buffers. It also initializes the playlist data structure by supplying the address and size of the memory buffers holding the data in the appropriate data structure fields.
VOID SetupPlayList( VOID )
{
/*
* This array keeps the address of each audio chime file.
*/
static LONG *pulBaseAddress[ NUMBER_OF_CHIME_FILES ];
USHORT usChimeFileId; /* Chime audio file ID. */
ULONG ulSizeOfFile, /* Size of audio file. */
ulMemoryAllocationFlags = PAG_COMMIT | PAG_READ | PAG_WRITE;
for(usChimeFileId=0; usChimeFileId<NUMBER_OF_CHIME_FILES;
usChimeFileId++)
{
ulSizeOfFile = HowBigIsTheChimeFile( usChimeFileId );
/*
* If the returned file size is zero, there is a problem with the
* chime files. A message will already have been shown to the
* user by the HowBigIsTheChimeFile function so get out of
* this routine.
*/
if ( ulSizeOfFile == 0 )
{
return;
}
if ( (pulBaseAddress[ usChimeFileId ] = (LONG *)
malloc( ulSizeOfFile )) == (LONG *) NULL )
{
/*
* The memory for the waveform files cannot be allocated.
* Notify the user and return from this routine. No playlist can
* be created/played until memory is available.
*/
ShowAMessage(acStringBuffer[IDS_NORMAL_ERROR_MESSAGE_BOX_TEXT - 1 ],
IDS_CANNOT_GET_MEMORY, /* ID of the message to show. */
MB_OK | MB_INFORMATION |
MB_HELP | MB_APPLMODAL |
MB_MOVEABLE ); /* Style of the message box. */
return;
} /* End of IF allocation fails. */
/*
* Place the waveform files into the memory buffer that was just
* created.
*/
CopyWaveformIntoMemory(
pulBaseAddress[ usChimeFileId ],
ulSizeOfFile,
usChimeFileId );
/*
* Now that we've loaded the waveform into memory, we need to put
* its address and size into the playlist data statements that
* use this particular file.
*
* Its address must be placed into the data statement's first
* operand and its size must be placed in the data
* statement's second operand.
*
* For the four different playlists, one for each chime time
* (1/4, 1/2, 3/4 and 1 hour increments),
* the address of the chime file and its size will be loaded
* into each data statement of the Playlist.
*/
if ( usChimeFileId == 0 )
/* If we just loaded CLOCK1.WAV */
{
/*
* Put the address of this chime into the first operand of
* every data operation that uses this particular chime.
*/
apltPlayList[ 0 ][ 0 ].ulOperandOne = /* 1/4 hour 1st data op */
apltPlayList[ 1 ][ 0 ].ulOperandOne = /* 1/2 hour 1st data op */
apltPlayList[ 2 ][ 0 ].ulOperandOne = /* 3/4 hour 1st data op */
apltPlayList[ 2 ][ 2 ].ulOperandOne = /* 3/4 hour 3rd data op */
apltPlayList[ 3 ][ 0 ].ulOperandOne = /* 1 hour 1st data op */
apltPlayList[ 3 ][ 2 ].ulOperandOne = /* 1 hour 3rd data op */
(ULONG) pulBaseAddress[ usChimeFileId ]; /* address */
/*
* Now put the size of the file into the second operand of every
* data operation that uses this particular chime.
*/
apltPlayList[ 0 ][ 0 ].ulOperandTwo = /* 1/4 hour 1st data op */
apltPlayList[ 1 ][ 0 ].ulOperandTwo = /* 1/2 hour 1st data op */
apltPlayList[ 2 ][ 0 ].ulOperandTwo = /* 3/4 hour 1st data op */
apltPlayList[ 2 ][ 2 ].ulOperandTwo = /* 3/4 hour 3rd data op */
apltPlayList[ 3 ][ 0 ].ulOperandTwo = /* 1 hour 1st data op */
apltPlayList[ 3 ][ 2 ].ulOperandTwo = /* 1 hour 3rd data op */
ulSizeOfFile; /* size */
}
else
if ( usChimeFileId == 1 )
/* If we just loaded CLOCK2.WAV */
{
/*
* Put the address of this chime into the first operand of
* every data operation that uses this particular chime.
*/
apltPlayList[ 1 ][ 1 ].ulOperandOne = /* 1/2 hour 2nd data op */
apltPlayList[ 2 ][ 1 ].ulOperandOne = /* 3/4 hour 2nd data op */
apltPlayList[ 3 ][ 1 ].ulOperandOne = /* 1 hour 2nd data op */
apltPlayList[ 3 ][ 3 ].ulOperandOne = /* 1 hour 4th data op */
(ULONG) pulBaseAddress[ usChimeFileId ]; /* address */
/*
* Now put the size of the file into the second operand of every
* data operation that uses this particular chime.
*/
apltPlayList[ 1 ][ 1 ].ulOperandTwo = /* 1/2 hour 2nd data op */
apltPlayList[ 2 ][ 1 ].ulOperandTwo = /* 3/4 hour 2nd data op */
apltPlayList[ 3 ][ 1 ].ulOperandTwo = /* 1 hour 2nd data op */
apltPlayList[ 3 ][ 3 ].ulOperandTwo = /* 1 hour 4th data op */
ulSizeOfFile; /* size */
}
else
if ( usChimeFileId == 2 )
/* If we just loaded CLOCK3.WAV , */
/* (this is the gong part of the chime) */
{
/*
* Put the address of this chime into the first operand of
* every data operation that uses this particular chime.
*/
apltPlayList[ 3 ][ 5 ].ulOperandOne = /* 1 hour 5th data op */
(ULONG) pulBaseAddress[ usChimeFileId ];
/*
* Now put the size of the file into the second operand of every
* data operation that uses this particular chime.
*/
apltPlayList[ 3 ][ 5 ].ulOperandTwo = /* 1 hour 5th data op */
ulSizeOfFile;
}
} /* End of For loop of chime files. */
} /* End of SetupPlayList */
Dynamic Playlist Modification
An application can change a playlist dynamically to achieve various effects in controlling the data stream in memory. In the case of the Clock Sample program, if the chime is an hour chime, then the program will dynamically modify the loop instruction in the memory playlist structure. This is how the device playing the playlist knows how many times to play the chime. The variable usHour is assigned the valid hour in the UpdateTheClock procedure.
/*
* Set the playlist to loop for the number of hours to
* be played. The hour value will be placed into operand one
* of the loop instruction playlist structure.*/
apltPlayList[ HOUR_PLAYLIST ][ LOOP_LINE ].ulOperandOne =
((ULONG) (usHour));
Manipulations that entail atomic, or uninterruptible, operations are acceptable techniques for achieving special effects with playlists. For example, the modification of the 32-bit operation code of an instruction is considered to be an atomic operation. Changing BRANCH to NOP, NOP to BRANCH, or changing the loop count value of a LOOP instruction are examples of atomic operations that produce special effects.
Because the playlist processor is asynchronously interpreting instructions, care must be taken to avoid a situation where unexpected results can occur. For example, suppose a situation exists that enables the loop count field to be modified by the application at the same time that the LOOP instruction is being executed by the playlist processor. The playlist processor can overwrite the application modification with a loop count value based on the execution of the LOOP instruction.
More extensive modifications (particularly those to pointer and length parameters of DATA instructions) should also be performed with caution. You do not want the playlist processor to gain control of a partially modified instruction.
As a rule, it is not a good idea to modify a DATA instruction unless you are sure the playlist processor cannot interpret the instruction during the modification process; that is, you know the instruction is currently unreachable by the processor. One way to determine the location of the processor is with the MESSAGE instruction. For example, suppose you precede a LOOP instruction with a MESSAGE instruction. When the message is returned to your application, you know the processor is busy with the loop and you can safely modify any DATA instructions outside the loop.
After DATA instructions have been modified, BRANCH or NOP instructions can be changed to direct playlist interpretation to the modified section of the playlist.
Using a Playlist for Recording
Memory playlists can be used in recording scenarios. For example, MESSAGE instructions are useful for notifying the application of the progress of a recording operation. A message can be sent each time the filling of a buffer is completed.
Encasing several DATA statements inside a loop using either a LOOP or BRANCH instruction allows the playlist to function as a simple circular buffering scheme. The following is an example of circular buffering:
0: NOP 1: DATA... 2: MESSAGE... 3: DATA... 4: MESSAGE... 5: DATA... 6: MESSAGE... 7: BRANCH 0
If the playlist processor executes an EXIT statement while recording, this means the playlist is full. This situation is similar to a disk becoming full during a recording operation. An ERROR_END_OF_PLAYLIST error is generated by the playlist processor in the streaming subsystem. As the recording operation continues, additional data is lost. The media driver being used returns the MCIERR_TARGET_DEVICE_FULL to the application.
LOOP, CALL, and RETURN instructions are used for iterative playing operations. These instructions are not appropriate for a recording scenario because recorded data residing in the buffer would be overwritten by each iteration.
Editing Operations
Applications can perform editing operations using both the PM clipboard and user-defined buffers. This enables applications to share data with other applications by way of the clipboard or to quickly retrieve data into user-defined buffers. Messages used for editing include MCI_COPY, MCI_CUT, MCI_DELETE, MCI_PASTE, MCI_REDO, and MCI_UNDO.
Clipboard and Resource Formats
Applications can imbed multimedia data into program resources and interchange that data through the clipboard.
The following clipboard and resource format types are defined in the OS2MEDEF.H file:
- CF_RMID and RT_RMID represent RIFF data that has a RMID chunk or regular MIDI with an "MT" header. This is a special case.
- CF_RIFF and RT_RIFF represent RIFF data including all of the headers.
- CF_WAVE and RT_WAVE represent RIFF data that has a WAVE chunk. This is a specific form of RIFF.
- CF_AVI and RT_AVI represent RIFF Audio/Video Interleaved (AVI) data. This is a specific form of RIFF. An entire video file is placed in the clipboard.
The following code fragment is an example of retrieving digital audio information from the clipboard. Source code for ADMCEDIT is located in the \TOOLKIT\SAMPLES\MM\ADMCT subdirectory.
{HAB habClipboard;
HMQ hmq;
HMMIO hmmioMem; /* handle to memory file */
MMIOINFO mmioinfo; /* info struct for memory file */
ULONG ulFormatInfo = 0;
ULONG rc;
PULONG pulDataSize;
LONG lBytesRead;
LONG lReturnCode;
MMAUDIOHEADER mmaudioheader;
MMAUDIOHEADER mmaudioheaderTemp;
PSZ pTempBuffer;
PVOID pNewBuffer;
habClipboard = WinInitialize( 0 );
if ( !habClipboard )
{
return ( MCIERR_CLIPBOARD_ERROR );
}
hmq = WinCreateMsgQueue( habClipboard, 0 );
if ( !hmq )
{
fCreatedMQ = FALSE;
}
/* habClipboard = WinQueryAnchorBlock( HWND_DESKTOP ); */
/*****************************************************************
* Check to see if there is a wave (CF_WAVE is the defined type) in
* the clipboard.
******************************************************************/
rc = WinQueryClipbrdFmtInfo( habClipboard,
CF_WAVE,
&ulFormatInfo );
if ( !WinOpenClipbrd( habClipboard ) )
{
WinCloseClipbrd( habClipboard );
return ( MCIERR_CLIPBOARD_ERROR );
}
pTempBuffer = ( PSZ ) WinQueryClipbrdData( habClipboard, CF_WAVE );
if ( !pTempBuffer )
{
WinCloseClipbrd( habClipboard );
return ( MCIERR_CLIPBOARD_ERROR );
}
/*****************************************************************
* We need to find out how much data is in the file. Retrieve
* the length of the RIFF chunk.
******************************************************************/
pulDataSize = ( PULONG ) pTempBuffer + 1;
memset( &mmioinfo, '\0', sizeof( MMIOINFO ) );
/*****************************************************************
* Prepare to open a memory file--the buffer * in the clipboard
* contains the actual RIFF file which the WAVE IOProc already knows
* how to parse--use it to retrieve the information and keep the MCD
* from file-format dependence.
******************************************************************/
mmioinfo.fccIOProc = mmioFOURCC( 'W', 'A', 'V', 'E' ) ;
mmioinfo.fccChildIOProc = FOURCC_MEM;
rc = CheckMem ( (PVOID) pulDataSize,
sizeof ( ULONG ),
PAG_READ | PAG_WRITE );
if (rc != MCIERR_SUCCESS)
{
WinCloseClipbrd( habClipboard );
return (MCIERR_OUT_OF_MEMORY );
}
mmioinfo.cchBuffer = ( *pulDataSize) + 8;
mmioinfo.pchBuffer = pTempBuffer;
hmmioMem = mmioOpen( NULL,
&mmioinfo,
MMIO_READ );
if ( !hmmioMem )
{
WinCloseClipbrd( habClipboard );
return ( mmioinfo.ulErrorRet );
}
Audio Media Driver Clipboard Commands
The data that the buffer or clipboard contains will be defined differently for each media driver.
The following formula is necessary to allocate memory for digital audio clipboard operations:
$$(BitsPerSample / 8) \times (SamplesPerSecond) \times (Channels) \times (Seconds)$$
Therefore, if an application is to copy 20 seconds of a 16-bit, 44 kHz, mono file into the clipboard using the caller's buffer; 1,764,000 bytes ((16/8) x (44100) x (1) x (20)) must be allocated and placed in the pBuff field of the MCI_EDIT_PARMS structure. For MCI_CUT, MCI_COPY, and MCI_PASTE, if MCI_TO_BUFFER or MCI_FROM_BUFFER is passed in, then the pBuff field should contain a valid pointer.
MCI_STATUS_CLIPBOARD returns MCI_TRUE if digital audio is in the clipboard; otherwise it returns MCI_FALSE. MCI_CUT removes the specified range and places the data in the buffer or clipboard. The position of the media will either be the from position if MCI_FROM is specified or the previous position if MCI_FROM is not specified. If the buffer is not large enough for the data an MCIERR_INVALID_BUFFER is returned. The units of MCI_FROM and MCI_TO must be supplied in the currently selected time format. If neither MCI_FROM or MCI_TO are specified, the operation will start from the current file position and continue to the end of the file. If audio data is already in the clipboard, it will be overwritten.
Note: The clipboard contents are emptied before the cut occurs.
MCI_COPY copies the specified range and places the data in the buffer or clipboard. The position of the media remains the same as it was before the copy operation.
MCI_PASTE deletes the selected range if the differences between the from and to position are greater than zero, then inserts the data provided in the buffer or clipboard. The media position will be at the end of what was pasted into the file. If neither MCI_FROM or MCI_TO are specified, MCI_PASTE inserts the clipboard contents at the current position. MCI_CONVERT_FORMAT converts the data that was in the clipboard to the destination file format. The following data format conversions can be performed:
- 16-bit to 8-bit resolution/8-bit to 16-bit resolution
- 11.025 kHz, 22.05 kHz, and 44.1 kHz to any of the following sampling rates: 11.025 kHz, 22.05 kHz, or 44.1 kHz
- mono to stereo/stereo to mono
Note: The MCI_CONVERT_FORMAT flag supports only the Pulse Code Modulation (PCM) format. The data format conversion can take a while to complete. If the notify flag is specified, the application is notified when the conversion is completed.
The following code fragment shows an example of the use of MCI_COPY and MCI_PASTE.
ULONG weMciCopy( HWND hwnd, ULONG ulMarkedStartBytes,
ULONG ulMarkedEndBytes, USHORT usDeviceID )
{
ULONG ulFlags;
MCI_EDIT_PARMS mcieditstr;
ULONG ulResult;
ulResult = 0L;
/*
* First, set all fields of the MCI_EDIT_PARMS structure to 0.
*/
memset( &mcieditstr, '\0', sizeof(MCI_EDIT_PARMS) );
/*
* The flags are NOTIFY, FROM, and TO.
*/
ulFlags = 0L;
ulFlags |= MCI_NOTIFY |
MCI_FROM |
MCI_TO;
mcieditstr.ulCallback = (ULONG)hwnd;
/*
* Set the from and to items to the beginning and end
* of the selected area.
*/
mcieditstr.ulFrom = ulMarkedStartBytes;
mcieditstr.ulTo = ulMarkedEndBytes;
ulResult = mciSendCommand( usDeviceID,
MCI_COPY,
ulFlags,
(ULONG)&mcieditstr,
0 );
return( ulResult );
}
ULONG weMciPaste( HWND hwnd, ULONG ulMarkedStartBytes,
ULONG ulMarkedEndBytes, USHORT usDeviceID )
{
ULONG ulFlags;
MCI_EDIT_PARMS mcieditstr;
ULONG ulResult;
ulResult = 0L;
/*
* First, set all fields of the MCI_EDIT_PARMS structure to 0.
*/
memset( &mcieditstr, '\0', sizeof(MCI_EDIT_PARMS) );
mcieditstr.ulCallback = (ULONG)hwnd;
ulFlags = 0L;
/*
* If there is an area of wave selected, then the flags are NOTIFY,
* FROM, TO, and CONVERT_FORMAT.
*/
if( ulMarkedEndBytes > ulMarkedStartBytes )
{
ulFlags |= MCI_NOTIFY |
MCI_FROM |
MCI_TO |
MCI_CONVERT_FORMAT;
/*
* Set the from and to items to the beginning and end
* of the selected area.
*/
mcieditstr.ulFrom = ulMarkedStartBytes;
mcieditstr.ulTo = ulMarkedEndBytes;
}
else
{
/*
* Otherwise, nothing in the wave is selected so the flags are
* only NOTIFY and CONVERT_FORMAT.
*/
ulFlags |= MCI_NOTIFY |
MCI_CONVERT_FORMAT;
/*
* Because this is a paste operation without FROM/TO,
* we have to SEEK so that the media position is set
* to the place that we want to paste.
*/
if( ulResult = weMciCall( hwnd,
MCI_SEEK ) )
return( ulResult );
}
ulResult = mciSendCommand( usDeviceID,
MCI_PASTE,
ulFlags,
(ULONG)&mcieditstr,
0 );
return( ulResult );
}
The following is an example of using the command string interface with editing commands to create a repeating sound.
open test.wav alias a wait copy a from 0 to 3000 wait seek a to end paste a wait paste a wait paste a wait
Device Sharing By Applications
The multimedia system supports sharing of physical devices among multiple applications. If a device is capable of being shared; that is, if it can maintain state information, the system can establish a unique device state, much like a Presentation Manager device context, for each application that uses the device.
The scope of a device state is defined by each device. The state of a simple device like the digital video player contains information about the current frame position, whether the device is playing or stopped, what its current playback speed is set to, and so on. The state of a compound device can include the name of the currently selected file, RIFF object, and playback position.
Media devices vary in their ability to support multiple device contexts concurrently. The different types of device use that are supported by media devices are:
- Fixed single-context
- Dynamic single-context
- Limited multiple-context
- Unlimited context
The following table contains descriptions and examples of these device use types.
| Context Use Type | Description |
|---|---|
| Fixed single-context | A fixed single-context device can establish only one device context. The state of a fixed single-context device cannot be queried or set by software.
An example of a fixed single-context device is a video cassette recorder that does not report the tape position to the driver. |
| Dynamic single-context | A dynamic single-context device is serially shareable. That is, the device can be used by only one application at a time but can be passed from one application to another. A device state for each application is saved and restored appropriately.
This is the most common concurrent use type for a media device. An example of a dynamic single-context device is a CD-ROM player. |
| Limited multiple-context | A limited multiple-context device can establish multiple device contexts, but the number of device contexts is limited by the physical device.
An example of a limited multiple-context device is a 4-channel amp-mixer audio device, which can concurrently support any of the following multiple-contexts: Four monaural contexts, two stereo contexts, and one stereo and two monaural contexts. |
| Unlimited context | An unlimited context device can support an arbitrary number of concurrent device contexts. The number of concurrent contexts is limited only by the resource limits of the system. |
The MM_MCIPASSDEVICE message sent with WinPostMsg by the multimedia system to applications and the MCI_ACQUIREDEVICE message sent by applications with mciSendCommand to the multimedia system provide a device-sharing scheme for the OS/2 multimedia environment.
To participate in device sharing, an application issues MCI_OPEN with the MCI_OPEN_SHAREABLE flag set. The system then attempts to acquire the device for the application. The application must wait until it receives the asynchronous MM_MCIPASSDEVICE message to gain control of the device. The multimedia system sends the MM_MCIPASSDEVICE message to inform the application that the device context is becoming active (MCI_GAINING_USE).
Before an application receives an MM_MCIPASSDEVICE message with an event of MCI_GAINING_USE, it can make inquiries about the device and the media. MCI_STATUS, MCI_GETDEVCAPS, MCI_INFO, and MCI_CLOSE commands can be sent to an inactive device context.
Note: If your application has set an MCI_NOTIFY flag on the open request, notification will be posted to the application before the MM_MCIPASSDEVICE message is sent. However, if the application message queue has other messages already queued, it is possible that the application may receive the MM_MCIPASSDEVICE message before it receives the notification message.
The active instance of the application remains active until the application returns from the WinPostMsg (MCI_LOSING_USE). This guarantees that the application has an active device context until it returns from WinPostMsg. If the application receives an MM_MCIPASSDEVICE message with an event of MCI_GAINING_USE, it should return immediately. The following code fragment illustrates the device sharing architecture from the Clock Sample program.
<syntaxhighlight lang="c">
/* * The next two messages are handled so that the Clock application * can participate in device sharing. Because it opens the devices * as shareable devices, other applications can gain control of the * devices. When this happens, we will receive a pass device * message. We keep track of this device passing in the fPassed * boolean variable. * If we do not have access to the device when we receive an * activate message, then we will issue an acquire device command * to gain access to the device. */
case MM_MCIPASSDEVICE:
if (SHORT1FROMMP(mp2) == MCI_GAINING_USE)
{
fPassed = FALSE; /* Gaining control of device */
} else
{
fPassed = TRUE; /* Losing control of device */
}
return( WinDefSecondaryWindowProc( hwnd, msg, mp1, mp2 ) );
case WM_ACTIVATE:
/* We use the WM_ACTIVATE message to participate in device sharing. * We first check to see if this is an activate or a deactivate * message (indicated by mp1). Then, we check to see if we've * passed control of the device that we use. If these conditions * are true, we issue an acquire device command to regain * control of the device, because we're now the active window on * the screen. * * This is one possible method that can be used to implement * device sharing. For applications that are more complex * than this sample program, developers may wish to take * advantage of a more robust method of device sharing. * This can be done by using the MCI_ACQUIRE_QUEUE flag on * the MCI_ACQUIREDEVICE command. */
/* * First we check to see if we've passed control of the device */
if ((BOOL)mp1 && fPassed == TRUE) {
mciGenericParms.hwndCallback = hwnd;
ulError = mciSendCommand( mciOpenParameters.usDeviceID,
MCI_ACQUIREDEVICE,
(ULONG)MCI_NOTIFY,
(PVOID) &mciGenericParms,
(USHORT)NULL);
if (ulError)
{
ShowAMessage(acStringBuffer[IDS_NORMAL_ERROR_MESSAGE_BOX_TEXT-1];
IDS_CHIME_FILE_ERROR, /* ID of message */
MB_OK | MB_INFORMATION |
MB_HELP | MB_APPLMODAL |
MB_MOVEABLE ); /* Style of msg box. */
}
}
return( WinDefSecondaryWindowProc( hwnd, msg, mp1, mp2 ) );
</syntaxhighlight>
An application regains control of a shared device by issuing the MCI_ACQUIREDEVICE message with mciSendCommand after it has received a WM_ACTIVATE message. The application receives a WM_ACTIVATE message whenever its frame window is activated or deactivated by user selection. The time for the application to regain control of a shared device is during the period its window is activated. A "greedy" application that grabs back a device as soon as it loses it defeats the purpose of the WM_ACTIVATE message processing scheme, which is to give control of a shared device to the application with which the user is interacting.
Only dynamic single-context and limited multiple-context devices are acquired by applications. The MCI_ACQUIREDEVICE function does not perform any function for fixed single-context and unlimited-context devices, because device contexts are not saved or restored for these classes of devices.
To better understand the allocation of resources to multiple device contexts, imagine a stack of device contexts. The physical device is associated with the topmost device context on the stack. Whenever a device context is opened, it is placed on top of the stack, and the physical device is associated with the new device context. When MCI_ACQUIREDEVICE is issued for a particular device context, that device context moves to the top of the stack, and the physical device is associated with the existing device context. Closing a device context removes it from the stack.
Queued Acquire Command
Setting the MCI_ACQUIRE_QUEUE flag of the MCI_ACQUIREDEVICE message enables the message to be queued and executed as soon as device resources become available. An application can issue an MCI_ACQUIREDEVICE message and, at a later point, the device context becomes active. This is true if either the MCI_NOTIFY or MCI_WAIT flag is specified. If the MCI_WAIT flag is specified, the calling thread is blocked until the device context becomes active. If the MCI_ACQUIREDEVICE request can be satisfied immediately, the command is not queued.
The acquire command can be used to acquire a device instance when the resource becomes available:
open music1.wave alias wave1 shareable wait play wave1 notify . . . ** During this time a losing use message is received ** ** and this instance becomes inactive. ** . . . acquire wave1 queue notify
If an MCI_ACQUIREDEVICE is queued and an application issues MCI_RELEASEDEVICE or MCI_CLOSE for that instance, the queued MCI_ACQUIREDEVICE message is canceled.
Releasing the Resource
The release resource command is used in conjunction with the queued acquire command. An application can release a device instance from the active state and make the next available inactive device instance active by setting the MCI_RETURN_RESOURCE flag of the MCI_RELEASEDEVICE message. When a device instance no longer needs its resources, the device instance can give up the resource to another device requesting the resources (with MCI_ACQUIRE_QUEUE).
The release command as shown in the following example can be used to release exclusive hold on a device.
open waveaudio alias wave2 shareable wait acquire wave2 exclusive wait record wave2 notify . . . ** Open the device exclusively to avoid interruptions ** ** during recording. ** . . . stop wave2 wait release wave2 return resource wait
The device instance will not be made active again unless an application issues an MCI_ACQUIREDEVICE message for this device context. This function is ignored if the instance is already in an inactive state. The instance remains active if the resource used by this instance is not required by any other instance.
Using a Device Exclusively
There are times when an application must retain control of the physical resource, such as the during the duration of a recording operation or when the application needs to establish specific settings for the device context. The application can retain control by not specifying the shareable flag with the open request or by setting the MCI_EXCLUSIVE flag of the MCI_ACQUIREDEVICE message. When a device has been acquired for exclusive use, other applications cannot acquire the device until the application using the device closes it or releases it from exclusive use with the MCI_RELEASEDEVICE message. When an application releases a device from exclusive use, it does not lose use of the device until another application acquires it.
When an application needs to acquire a device context for exclusive use without acquiring the entire device resource, the application can set the MCI_EXCLUSIVE_INSTANCE flag of the MCI_ACQUIREDEVICE message. This flag prevents the device context from being made inactive unless the application using the device issues the MCI_CLOSE or MCI_RELEASEDEVICE message.
The MCI_EXCLUSIVE_INSTANCE and MCI_EXCLUSIVE flags are mutually exclusive. An instance can be in one of three sharing states:
- Instance exclusive
- Device exclusive
- Fully shareable
Using the MCI_EXCLUSIVE_INSTANCE flag places an instance in an instance-exclusive sharing state. Using the MCI_EXCLUSIVE flag places an instance in a device-exclusive sharing state. The MCI_RELEASEDEVICE message places an instance in a fully shareable state.
Device Groups
When an OS/2 multimedia application needs to control more than one device at a time, it uses the MCI_GROUP message to group device contexts. The MCI_GROUP_MAKE and MCI_GROUP_DELETE flags are used to make and delete groups.
MCI_GROUP_MAKE ties several device instances together so that a single command sent to the group by an application is actually sent to each device instance in the group by the multimedia system. This flag can be combined with any of the other MCI_GROUP flags except MCI_GROUP_DELETE in which case an MCIERR_FLAGS_NOT_COMPATIBLE error code is returned. Device instances must have been previously opened but can be in any mode (such as, playing, stopped, or paused) for this message to be successful. If one or more device IDs are invalid, the MCIERR_INVALID_DEVICE_ID error code is returned. If a device ID or alias refers to a device in another group, the MCIERR_ID_ALREADY_IN_GROUP error code is returned.
Once a group has been made, certain command messages sent to the group ID (or alias name) are sent to each device making up that group. Command messages that support groups are:
| MCI_ACQUIREDEVICE | MCI_RELEASEDEVICE |
| MCI_CLOSE | MCI_RESUME |
| MCI_CUE | MCI_SEEK |
| MCI_PAUSE | MCI_SET |
| MCI_PLAY | MCI_STOP |
| MCI_RECORD |
Note: Commands sent to a group must use the MCI_NOTIFY flag.
To end a group association, an application uses the MCI_GROUP_DELETE flag of the MCI_GROUP message. None of the other flags of the MCI_GROUP message can be combined with MCI_GROUP_DELETE because the only information required by this flag is a group ID. If any other flags are supplied an MCIERR_FLAGS_NOT_COMPATIBLE error code is returned. The MCIERR_INVALID_GROUP_ID error code is returned if an application passes an invalid ID. When a device in a group is closed, it is removed from the group. When the last device in a group is closed, the group is automatically deleted.
Applications can use the MCI_GROUP_ALIAS flag to refer to a group by a name rather than a group ID for use with the mciSendString interface. This flag can only be used with an MCI_GROUP_MAKE flag; the given alias is used to refer to the new group. If the alias is already in use, an MCIERR_DUPLICATE_ALIAS error code is returned. Each string group "make" command must include an alias so the group can later be referred to. The alias follows the group command as shown by the following syntax:
group grp1 make (wave1 cd1) wait
This defines the alias to be "grp1". The list of device names (members of the group) is delimited by parenthesis and separated by spaces and optional quotation marks. The following syntax is used to delete this group:
group grp1 delete wait
Duet Player Sample Program Example
The following code fragment illustrates the creation of a device group in the Duet Player I sample program. An array is filled with the IDs of opened devices to be associated in the group. The application then calls MCI_GROUP to create the group and return a handle.
/* If this is the first time through this routine, then we need to
* open the devices and make the group.
*
* On subsequent calls to this routine, the devices are already open
* and the group is already made, so we only need to load the
* appropriate files onto the devices.
*/
{
/*
* Open one part of the duet. The first step is to initialize an
* MCI_OPEN_PARMS data structure with the appropriate information,
* then issue the MCI_OPEN command with the mciSendCommand function.
* We will be using an open with only the element name specified.
* This will cause the default connection, as specified in the
* MMPM.INI file, for the data type.
*/
mopDuetPart.hwndCallback = hwnd; /* For MM_MCIPASSDEVICE */
mopDuetPart.usDeviceID = (USHORT) NULL; /* this is returned */
mopDuetPart.pszDeviceType = (PSZ) NULL; /* using default conn.*/
mopDuetPart.pszElementName = (PSZ) aDuet[sDuet].achPart1;
ulError = mciSendCommand( (USHORT) 0,
MCI_OPEN,
MCI_WAIT | MCI_OPEN_ELEMENT |
MCI_OPEN_SHAREABLE | MCI_READONLY,
(PVOID) &mopDuetPart,
UP_OPEN);
if (!ulError) /* if we opened part 1 */
{
usDuetPart1ID = mopDuetPart.usDeviceID;
/*
* Now, open the other part.
*/
mopDuetPart.pszElementName = (PSZ) aDuet[sDuet].achPart2;
ulError = mciSendCommand( (USHORT) 0,
MCI_OPEN,
MCI_WAIT | MCI_OPEN_ELEMENT |
MCI_OPEN_SHAREABLE | MCI_READONLY,
(PVOID) &mopDuetPart,
UP_OPEN);
if (!ulError) /* if we opened part 2 */
{
usDuetPart2ID = mopDuetPart.usDeviceID;
/*
* Now we need to create a group. To do this,
* we need to fill an array with the IDs of the already open
* devices that we want to group. Then we call MCI_GROUP to
* create the group and return a handle to it.
*/
ulDeviceList[0] = (ULONG)usDuetPart1ID;
ulDeviceList[1] = (ULONG)usDuetPart2ID;
mgpGroupParms.hwndCallback = (HWND) NULL; /* Not needed -
we're waiting */
mgpGroupParms.ulNumDevices = NUM_PARTS; /* Count of devices */
mgpGroupParms.paulDeviceID = (PULONG)&ulDeviceList; /* Array of
devices */
mgpGroupParms.ulStructLength = sizeof (mgpGroupParms);
ulError = mciSendCommand( (USHORT) 0,
MCI_GROUP,
MCI_WAIT | MCI_GROUP_MAKE|
MCI_NOPIECEMEAL,
(PVOID) &mgpGroupParms,
UP_GROUP);
fFirstPlay = FALSE;
Resource Allocation
An application avoids piecemeal resource allocation problems by setting the MCI_NOPIECEMEAL flag of the MCI_GROUP message. This flag specifies that the associated group is treated as a whole rather than several separate instances. If one of the device instances becomes inactive then all the instances in the group will go inactive. This flag can only be combined with the MCI_GROUP_MAKE flag as it specifies the nature of the group to be created. If the MCI_NOPIECEMEAL flag is set during creation and one or more of the instances in the list of IDs or aliases is already inactive, the entire group will go inactive and each device in the group saves its state. The device contexts in the group can subsequently be restored by passing the group device context ID with the MCI_ACQUIREDEVICE message, using mciSendCommand.
If the MCI_NOPIECEMEAL flag is not specified and devices are lost, the application retains control over the remaining devices in the group, unless one of the lost devices is the master of the group. When the master of a group of devices is lost, the group is lost.
Event Synchronization
Applications can perform event synchronization by taking an action at a specified point during the playback of a data object. There are two ways an application can do this:
- The application can request to be notified when a specified point in playback is encountered by sending the MCI_SET_CUEPOINT message to the multimedia system. When this cue point is encountered, the multimedia system sends an MM_MCICUEPOINT message to the application.
- The application can request notification on a periodic basis, based on time or position, by sending the MCI_SET_POSITION_ADVISE message to the multimedia system. As each time period (or position) specified passes, the multimedia system sends an MM_MCIPOSITIONCHANGE message to the application.
Cue Points
Cue points are discrete locations or time positions in a media device. When a device encounters a time position associated with a cue point, a message is returned to the application window handle that is specified to receive the cue point messages. Cue points are maintained as part of a device context, so setting a cue point in one device context will not cause cue point messages to be generated for other device contexts.
Applications specify cue points for a device with the MCI_SET_CUEPOINT message. A cue point is identified by its location; setting a cue point "on" sets a cue point at the specified location, and setting a cue point "off" removes the cue point. Because cue points are identified by location, only one cue point can be set at a specified location in the media. Therefore, setting a cue point at a location where a cue point is already set causes the second MCI_SET_CUEPOINT to fail and to return the error MCIERR_DUPLICATE_CUEPOINT. Cue points can be set at any valid location in the media, regardless of current media position. If a device is currently playing at 2:00 (two minutes), and a cue point is set at 1:00 (one minute) in the media, and the device is subsequently seeked and played from the beginning, the cue point message will be generated when the device passes the 1:00 point in the media.
Cue points are persistent. That is, they remain set after they are encountered. The device will generate cue point messages whenever the cue point location is encountered, which may be many times if the device is seeked or played repeatedly. Cue points are encountered only when a device is playing or recording. If a device is seeked from its current position to some new position, cue points set at locations between the old and new position are not encountered during the seek operation, and no cue point messages are generated.
Because cue points can be set only within the valid range of a media element, cue points cannot be set when a file is not loaded. All cue points for a device context are removed when a new file element is loaded.
Cue points also can be created as part of a media element. In the case of cue points imbedded directly in a media element, the MCI_SET_CUEPOINT message performs no function. Imbedded cue points always result in cue point messages being returned when they are encountered. The user parameter value returned on the cue point message varies from one media data type to another and should be set to a meaning that is significant to the application.
When a cue point is encountered, an MM_MCICUEPOINT message is sent to the window specified by the hwndCallback field of the MCI_CUEPOINT_PARMS data structure passed with the MCI_SET_CUEPOINT message. The MM_MCICUEPOINT message parameters contain the device ID of the device context that generated the cue point message, as well as the media position and an additional application-defined parameter that can be specified when the cue point is set. Although the media position specified by the application on the MCI_SET_CUEPOINT message is in the currently set device units, the media position returned on the MM_MCICUEPOINT message is always in MMTIME units. MMTIME units are used because the time format set when the cue point is set might not be the same time format set when the cue point is encountered.
The maximum number of cue points that can be set in a device context is defined by the implementation of the logical device. Devices generally support up to 20 cue points per device context.
Position Advises
In addition to notification messages at discrete locations in the media, periodic notification of elapsed media time can also be requested. These periodic messages, referred to as "position advise" messages, are requested for a device context based on a specified time interval. Position advise messages are requested by issuing the MCI_SET_POSITION_ADVISE message to a device context as shown in the following code fragment.
MCI_OPEN_PARMS mop;
static MCI_PLAY_PARMS mpp; /* parms for MCI_PLAY */
static MCI_POSITION_PARMS mppPos; /* parms for
MCI_SET_POSITION_ADVISE */
iState = ST_PLAYING; /* Set state to reflect play mode */
mppPos.hwndCallback = hwndMainDlg;
mppPos.ulUnits = 1500; /* Request position advise messages */
mppPos.usUserParm = usPositionUP;
mppPos.Reserved0 = 0;
mciSendCommand ( mop.usDeviceID,
MCI_SET_POSITION_ADVISE,
MCI_NOTIFY | MCI_SET_POSITION_ADVISE_ON,
(PVOID) &mppPos,
UP_POSITION );
This causes MM_MCIPOSITIONCHANGE messages to be returned to the application window specified in the MCI_POSITION_PARMS structure at the requested frequency as media time passes in the device context. Only one position advise frequency may be active for a device context, and having position advise notification active in one device context does not cause messages to be generated in other device contexts. Position advise messages can be set only when a device element is loaded in the device context, and are reset when a new device element is loaded.
Like MM_MCICUEPOINT messages, MM_MCIPOSITIONCHANGE message parameters contain the device ID of the device context that generated the position advise message, as well as the media position and an additional application-defined parameter that can be specified when the position advise notification is requested. Although the media position interval (frequency) specified by the application on the MCI_SET_POSITION_ADVISE message is in the currently set device units, the media position returned on the MM_MCIPOSITIONCHANGE message is always in MMTIME units. MMTIME units are used because the time format set when the position advise notification is set might not be the same time format set when the position advise notification messages are returned.
Position advise notifications are generated only during playback or recording. MM_MCIPOSITIONCHANGE messages are usually not generated during seek operations initiated by the application. The exception is a device, such as a tape recorder, that has a discernible position during the seek operation. A device like this can generate position advise messages as the media is traversed, to indicate the progress of the seek operation.
The following code fragment shows how the Caption Sample application handles the MM_MCIPOSITIONCHANGE message. When the Caption Sample application receives a position change message, it updates its media position slider arm allowing the application to advance the media position slider smoothly as the audio plays.
case MM_MCIPOSITIONCHANGE:
/*
* This message will be returned (in MMTIME) to the application
* whenever the audio position changes. This time will be used to
* increment the audio position slider. This message is only
* generated during playback.
*/
if ( eState == ST_PLAYING )
{
ulTime = (ULONG) LONGFROMMP(mp2);
/*
* Get the new slider arm position and set it.
*/
sArmPosition =
(SHORT) ( ( ulTime * ( sAudioArmRange - 1) ) / ulAudioLength );
WinSendMsg(
hwndAudioSlider,
SLM_SETSLIDERINFO,
MPFROM2SHORT( SMA_SLIDERARMPOSITION, SMA_RANGEVALUE ),
MPFROMSHORT( sArmPosition ));
}
return 0;
System Values
The OS/2 multimedia system provides a number of system-wide values that can be queried and set by applications. Because OS/2 multimedia applications such as Volume Control and Multimedia Setup allow users to set system values, it is recommended that applications only query the settings users have selected. The following table describes the system values that can be queried and set using mciQuerySysValue and mciSetSysValue.
| System Value | Description |
|---|---|
| MSV_CLOSEDCAPTION | Query or set the current state of a captioning flag. By querying the setting of this flag, an application can determine whether to display text along with audio, for example, for a hearing-impaired user. |
| MSV_MASTERVOLUME | Query or set the current master audio level. This value acts as a "multiplier" of the individual volume levels of each device context, allowing one application to control the volume for a number of open devices or elements. |
| MSV_HEADPHONES | Reserved for future use. |
| MSV_SPEAKERS | Reserved for future use. |
| MSV_WORKPATH | Query or set the directory for storing of temporary files by the media driver. This value can be used to point to, for example, a directory on the hard disk that holds waveform data from a recording operation. |
| MSV_SYSQOSERRORFLAG | Query the Quality of Service (QOS) error flag. By querying this flag, an application can determine an error occuring during band-width reservation. |
| MSV_SYSQOSVALUE | Query or set the QOS specification value. This system-wide Quality of Service (QOS) specification value is used for band-width reservation (for example, bytes per second) over the network. |
The following code fragment demonstrates how to obtain the multimedia work path.
CHAR szWorkpath[CCHMAXPATH] ; /* Work path for temporary files */
if ( mciQuerySysValue( MSV_WORKPATH, szWorkPath ) )
{
/* mciQuerySysValue was successful, szWorkPath now */
/* contains the multimedia workpath */
}
Clock Sample Program Caption Query
When it is time to chime the clock, the Clock Sample program checks the system captioning flag to determine whether or not it should display a visual chime while the audio chime is playing. The Clock program sets the global variable fClosedCaptionIsSet to store the value of the system captioning flag.
/* * If the Captioning Flag indicates that the bell should be * animated (swung), the region of the presentation space * that contains the bell bitmap is to be invalidated so that a * WM_PAINT will be sent to draw the bells. */ mciQuerySysValue( MSV_CAPTION, (PVOID)&fCaptionIsSet );