Multimedia I/O Procedures

By Lachlan O'Dea

Introduction
This is an article about I/O procedures, a small part of OS/2's large and complex multimedia system. I became interested in I/O procedures (hereafter known as IOProcs) as a means for reading, writing and displaying PNG images. PNG is a graphics file format similar to GIF and is pronounced 'ping'.

At first I planned on writing my own PM program for viewing PNG files, and actually developed a basic PNG viewer. I can't recall exactly how I got the idea of using an IOProc, but after reading the relevant information in the Toolkit docs I decided that writing an IOProc would be both an easier and superior approach.

I plan to write two articles about IOProcs. This article shall deal with IOProcs in general: what they are for, how they work and where they fit into the OS/2 multimedia system. The second article will be about the implementation of PNGIOProc, my own IOProc which reads and writes PNG images.

Please note that I am not an expert on this subject. What knowledge I have comes from reading the documentation in the Warp Toolkit and writing my own IOProc. I hope people find this article useful.

Before getting into the details of IOProcs, I'll briefly describe the multimedia subsystems and how they relate to each other and the OS/2 multimedia system in general.

The Multimedia Subsystem
The OS/2 multimedia system was designed with an extensible architecture, which allows developers to add support for new functions, multimedia devices and multimedia data formats. This extensible architecture is achieved through multimedia subsystems which can be installed to provide new functionality. There are three types of subsystems, each controlled by a manager.

Media Control Drivers
A Media Control Driver (MCD) provides access to the features of a multimedia device. Such devices include CD audio, digital video and the amplifier/mixer. To add support for a new media device, you must write an MCD to support that device. MCDs do not communicate directly with the hardware, instead they send commands to a physical device driver or use the other multimedia subsystems, such as the stream programming interface.

The various MCDs on the system are controlled by the Media Device Manager (MDM). Multimedia applications access the features of the MCDs through the MDM. When a process tries to access a device, the MDM makes sure that it does not interfere with other processes which might be using the device. The MDM also allows an application to synchronize the use of two or more devices.

Stream Handlers
Stream handlers are used for providing a continuous flow of multimedia data in a real-time manner. Typical use of stream handlers are digital audio, MIDI, and video. Stream handlers can run at ring 0 as a physical device driver or at ring 3 as a dynamic link library, depending on whether the stream handler needs to directly access the device. Stream handlers can make use of CODECs and I/O Procedures if desired.

The Synchronization and Streaming Manager (SSM) coordinates and synchronizes stream handlers. The SSM provides the stream programming interface (SPI) to higher- level multimedia components such as MCDs while providing helper services to both ring 0 and ring 3 stream handlers. The SSM also provides the synchronization features essential to multimedia applications.

I/O Procedures
IOProcs are used by the Multimedia I/O (MMIO) Manager to access multimedia data stored as a file. Although the term 'file' is used, IOProcs can be written to support storage mechanisms other than the standard file system, such as memory files.

The services of the MMIO manager can be used by applications or other parts of the multimedia system whenever they need to manipulate data as a file. Much of the MMIO API (mmioOpen, mmioRead, mmioClose) mimics the standard Dos API, but there are many extra features related to multimedia. An application uses the MMIO API by calling mmioOpen, which returns a handle to the 'file' (such handles are typically named with the prefix hmmio). This MMIO file handle is then used in all subsequent operations on the file, until the file is closed. While a MMIO handle is used in a similar way to a standard OS/2 file handle, they are not interchangeable.

The MMIO Manager also provides other services in the form of CODECs, or COmpressors/DECompressors. A CODEC is basically used to take a buffer full of data and either compress it or decompress it. If you want to support a new method of compression (perhaps a new type of AVI) then you can install a new CODEC which handles that compression method. CODECs can be used by IOProcs, applications or other parts of the multimedia system. Internally, CODECs have a similar architecture to that of IOProcs.

The remainder of this article will cover I/O Procedures in detail.

Four character codes (FOURCCs)
IOProcs (and CODECs) are identified within the MMIO system by a 32 bit quantity usually represented as a sequence of four characters. These four character sequences are called four character codes and have a data type of FOURCC. This is a much more convenient method of identification of IOProcs than the string holding the NLS name of the procedure.

Functions exist for manipulating FOURCCs, such as mmioFOURCC which makes a FOURCC out of four separate characters. A FOURCC can contain less than four characters, in which case it is padded on the right by spaces. The first character is placed into lowest byte of the 32 bits and the last character is placed in the highest byte.

FOURCCs are also used in other parts of the multimedia system, such as chunk IDs in RIFF files.

Storage System IOProcs
These process data which has a particular method of storage. Storage system IOProcs have no knowledge of the format of the data they are reading; they operate only on the storage mechanism used, not the actual data which is stored.

There are three storage system IOProcs which are provided internally by MMIO: Note that these IOProcs don't care what kind of data they are being used to store; it could be audio, video, image or any other kind of multimedia data. For example, the DOS IOProc can be used to read both WAV and AVI files from a hard disk, but it won't translate the data in any way.
 * DOS, which is used to access standard files in the OS/2 filesystem. The IOProc uses the standard OS/2 file services to do its work.
 * MEM, which is used to access memory as if it were a normal file.
 * CF, which supports the compound RIFF file format.

So why use a storage system IOProc to read a file when you could just use the standard Dos filesystem services? Because the Dos functions only work for files stored via the filesystem, whereas the MMIO functions provide a consistent interface for all storage mechanisms (a feature taken advantage of in file format IOProcs). New storage mechanisms can be easily supported by installing a new storage system IOProc.

Storage system IOProcs can be used by applications, as well as file format IOProcs.

File Format IOProcs
A file format IOProc processes multimedia data which is in a specific format. OS/2 comes with several standard file format IOProcs including WAVE, MIDI and AVI. My PNGIOProc example is a file format IOProc, as is the MMIOPROC sample in the Warp Toolkit. PNGIOProc supports files which are in the PNG format, whereas MMIOPROC supports files which are in the M-Motion still video format.

File format IOProcs perform all I/O using storage system IOProcs. This is where we see the advantage of using storage system IOProcs instead of more direct methods. As a file format IOProc uses a storage system IOProc to perform I/O, the file format IOProc doesn't care where or how the data is stored, it only cares what format the data is in. A file format IOProc can therefore access data using any mechanism which is supported by a storage system IOProc.

The file format supported by an IOProc must be of a particular media type, such as audio, image or video.

Translated or untranslated?
File format IOProcs have two modes of operation: translated and untranslated. Translation can be applied to the header and/or the data of the file. An IOProc can support both translated and untranslated modes if desired.

The PNGIOProc example operates only in translated mode for both the header and the image data. Translation involves converting the native format of the data to and from one of OS/2's standard formats. This allows multimedia applications to read and write data without knowing anything about the format it is stored in - they only need to know about the standard OS/2 formats.

This is how the image viewer that comes with the Bonus Pack works (IB.EXE - it is used by the light table to display images). When the user asks to view an image, MMIO determines which IOProc must be used. The file is then opened in translation mode and the IOProc converts the image to one OS/2's four standard bitmap formats, which the viewer application knows how to display.

Similarly, the Multimedia Data Converter (located in the Multimedia folder on the desktop) uses translation to convert images and digital audio to other formats. For example, a GIF image can be translated into a standard OS/2 bitmap by a GIF IOProc and then a PCX IOProc can be used to write the bitmap out as a PCX image. If you install PNGIOProc, the Multimedia Data Converter will be able to convert PNG to and from any other image format with an IOProc.

The main disadvantage of translation is that you are limited by OS/2's standard formats. If you convert a GIF to an OS/2 bitmap and then to a PNG, you lose any transparency information on the way, even though both GIF and PNG support transparency. Also, as PNGIOProc shows, the restrictions placed by the standard formats can make the translation process quite inefficient.

Each media type has a set of possible standard formats which an IOProc can translate to. For a media type of 'image', there are four OS/2 1.3 bitmap formats which can be used; for a media type of 'audio', three PCM formats can be used.

It also possible for an IOProc to operate in untranslated mode. Some of the multimedia documentation doesn't seem to make much sense when describing untranslated mode. For example, the Multimedia Subsystem Programming Guide has the following:

The default mode of access, untranslated, allows the caller to perform I/O of the file data in its native format. All header information and any data is written to a file and read from a file and presented at the caller level without modification.

To me, this seems to be saying that the IOProc simply passes data along without modifying it in any way. Why not just use the storage system IOProc directly? I'm still not sure what untranslated mode is useful for.

Untranslated mode has the disadvantage that applications have to be written for specific IOProcs - the application must understand the format-specific data that the IOProc returns. The advantage is that you are not limited by OS/2's standard formats and can use all the features available. For example, reading a PNG image in untranslated mode would allow for a progressive display using interlacing, but this is not possible using translated mode. Also, it is possible to do better and more efficient conversion to other (non-standard) formats.

IOProc Message Handling
An IOProc is basically a single procedure which processes MMIO messages. There are pre-defined messages, as well as user-defined messages which are specific to an application. Which pre-defined messages an IOProc must support depends on the type of the IOProc and what its capabilities are. Some messages must be supported by all IOProcs, however.

If an IOProc doesn't know how to handle a message, it must either pass the message to the child IOProc (if one exists) or return an error code. A child IOProc is the storage system IOProc which a file format IOProc is using to do I/O.

An IOProc is packaged in a DLL and accessed via a single function entry point: LONG IOProc_Entry( PVOID pmmioStruct,       USHORT usMsg,        LONG lParam1,        LONG lParam2 ) Figure 1) IOProc entrypoint prototype

Refer to the Multimedia Programming Reference for the complete details of all the MMIO messages.
 * pmmioStruct almost always points to an MMIOINFO structure. Whenever a file is opened using MMIO, an MMIOINFO structure is allocated and contains all the information about that file until it is closed. The IOProc should store whatever extra information it requires in its own structure and store a pointer to the structure in the pExtraInfoStruct field of MMIOINFO. This is analogous to a normal PM window procedure storing information in window words.
 * MMIOINFO is an important structure in all IOProcs, so I will give some details on some of the fields in the structure. Refer to the Multimedia Programming Reference in the Toolkit for more information.
 * ulFlags - the access flags which were specified when the file was opened. These specify if the file was opened for reading or for writing and also the file sharing mode.
 * fccIOProc - simply the FOURCC of the IOProc which handles the file.
 * pIOProc - pointer to the IOProc to use for this file.
 * ulErrorRet - an extended error return code which is used by some messages to return extra details about an error.
 * cchBuffer, pchBuffer, pchNext, pchEndRead, lBufOffset - I never needed to do anything with the buffer, so I didn't mess with these.
 * lDiskOffset - this is used internally by MMIO functions, so I didn't touch it.
 * aulInfo[4] - this can be used to hold state information, or pass extra parameters from mmioOpen (as is the case with the MEM storage system IOProc).
 * lLogicalFilePos - I'm not sure what this is for.
 * ulTranslate - this specifies whether header or data translation is being performed.
 * fccChildIOProc - this is the FOURCC of the child IOProc, if any.
 * pExtraInfoStruct - a pointer to a user defined structure containing information specific to the IOProc.
 * hmmio - the MMIO handle of the open file.
 * usMsg is a number which specifies the message which is being sent to the IOProc. Pre-defined messages have the prefix MMIOM. Application specific messages can be defined with values above that of MMIOM_USER.
 * lParam1 and lParam2 are two message-specific parameters similar to message parameters in standard PM window procedures.

Implementing a new IOProc basically involves writing an IOProc entry function which correctly handles all the required MMIO messages. Next month I will discuss in detail each of the messages handled by PNGIOProc.

Temporary Installation
An IOProc can be installed to process a particular file by calling mmioOpen with the address of the IOProc entry point. This has the effect of over-riding the default IOProc with the one the application wants. Once the file is closed, the IOProc will not be used again unless its address is passed in another call of mmioOpen.

To specify the IOProc to mmioOpen, store the address of the entry point in the pIOProc field of the MMIOINFO structure. Set the fccIOProc field to 0.

When using this method, the IOProc doesn't need to reside in a DLL. Naturally, a DLL must be used if the IOProc is to be used by multiple programs. Also, the entry point address can be resolved either at load-time or run-time.

Semi-permanent Installation
This method of installation uses the mmioInstallIOProc function which basically installs the IOProc for use by the current process. A list of installed IOProcs is kept for each process which uses MMIO functions. mmioInstallIOProc can be used to add to and remove from this list, as well as find a particular IOProc in the list.

As this function only affects the process which calls it, different processes can install different IOProcs with the same FOURCC without interfering with each other. If an IOProc is in a DLL to be shared by multiple applications, then each application must call mmioInstallIOProc before it can use the IOProc.

Once an application has called mmioInstallIOProc, the IOProc is automatically available for use by all subsequent calls to mmioOpen within that application.

Permanent Installation
An IOProc can be installed so that it is permanently available to all applications as soon as the system is started. This is done by placing the required information about the IOProc in the MMIO initialization file (MMPMMMIO.INI). An IOProc installed in this way is automatically available to any application that wants to use it; calling mmioInstallIOProc is not required.

This method of installation is really the only way you can take advantage of translation in IOProcs. It is also useful for untranslated IOProcs, however. When using this installation method, the IOProc must be in a DLL. Multiple IOProcs can reside in the same DLL, provided they each have a different name for their entry point function.

The IOProc information can be added to the INI file by calling mmioIniFileHandler with the flag MMIO_INSTALLPROC specified. An MMINIFILEINFO structure must also be passed containing the following information.
 * fccIOProc - the FOURCC which identifies the IOProc.
 * szDLLName - the name of the DLL containing the IOProc. If the DLL isn't in the LIBPATH (unlikely), you must specify the full path.
 * szProcName - the name of the entry point function.
 * ulExtendLen - this is the 'length of extended fields'. According the documentation, you just make it 16. I think it is the size of the last four fields.
 * ulMediaType - the media type the IOProc handles, e.g. MMIO_MEDIATYPE_MIDI.
 * ulIOProcType - specifies if the IOProc is of type storage system or file format.
 * mmioIniFileHandler may also be called to remove an IOProc, or to find an IOProc with particular characteristics.

The Multimedia Install Program
If you want to permanently install an IOProc on a user's system, it is probably more convenient to use the Multimedia Install Program (MINSTALL) rather than write your own program which calls mmioIniFileHandler. MINSTALL uses things called 'control files' which enable you to make appropriate changes to CONFIG.SYS and the INI files. You can also write an 'installation DLL' to further customize the installation process.

MINSTALL is very versatile, as it is designed to all install all kinds of multimedia software, not just IOProcs. I've never used it myself, but it doesn't look too tricky to install an IOProc with it. I won't go into details now, as MINSTALL is probably a big enough topic to deserve its own article.

I'll Be Back!
Hopefully I've managed to give you a decent introduction to what I/O Procedures do and how they work. Next month I'll present my home-made IOProc for reading and writing PNG images and give a detailed explanation of how it works. While PNGIOProc doesn't handle all of the MMIO messages, it will hopefully provide a good example of how to write an IOProc.

A significant portion of the code in PNGIOProc is based on a sample program in the Warp Toolkit called MMIOPROC. Other relevant sample programs are: CASECONV, a simple IOProc which performs case conversion on text; ULTIMOIO which implements a video IOProc using a CODEC; and MMBROWSE, an image browser application which uses IOProcs. Check out these sample programs if you are interested in IOProcs.