Jump to content

MMAPG - Resource Interchange File Format (RIFF) Services

From EDM2
Multimedia Application Programming Guide
  1. Introduction
  2. What's New...
  3. Multimedia Application Programming Environment
  4. Media Control Interface
  5. Multimedia Logical Devices
  6. Amplifier-Mixer Device
  7. Waveform Audio Device
  8. Sequencer Device
  9. CD Audio Device
  10. CD-XA Device
  11. Videodisc Device
  12. Digital Video Device
  13. Direct Interface Video Extensions (DIVE)
  14. Captioning
  15. OS/2 Multimedia Controls
  16. Multimedia I/O File Services
  17. Resource Interchange File Format (RIFF) Services
  18. Sample Application Programs
  19. Installing a Program Using MINSTALL
  20. Caption DLL
  21. High-Level Service API
  22. Notices
  23. Glossary

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation

The Resource Interchange File Format (RIFF) is the standard file format used for storing multimedia files. RIFF enables audio, image, animation, and other multimedia elements to be stored in a common format. RIFF is also used as the basis for defining new file formats for OS/2 multimedia software.

RIFF file I/O provides simple functions to locate, create, enter, exit, and access the chunk—the basic building block of a RIFF file. You can open, read from, and write to RIFF files the same way as other file types. Blocks of data are identified by tags. An advantage of tagged file formats is that an application can process blocks that it understands while ignoring blocks that do not concern it. RIFF can also be expanded upon (by adding new tags) without breaking existing applications.

A RIFF file created with mmioOpen can hold a single data object or, if it is built as a compound file, multiple data objects. Data objects in a compound file are referred to as chunks. Chunks in a compound file are its table of contents, and the multiple data objects stored in the resource group.

Note: Refer to the OS/2 Multimedia Programming Reference for detailed information on how to define an application using the RIFF tagged file structure.

RIFF File Structural Overview

A RIFF chunk begins with a chunk ID, which is a four-character code (FOURCC) that identifies the representation of the chunk data. A program reading a RIFF file can skip over any chunk whose chunk ID it does not recognize. The chunk ID is followed by a four-character chunk size (ULONG) specifying the size of the data field in the chunk. Lastly, it contains a data field containing the actual data of the chunk. If the chunk ID is

RIFF

, the first four characters of the data portion of the chunk are a form type; if the chunk ID is

LIST

, the first four characters are a list type. The only chunks allowed to contain other chunks (subchunks) are those with a chunk ID of

RIFF

or

LIST

. The first chunk in a RIFF file must be a RIFF chunk. All other chunks in the file are subchunks of the RIFF chunk as shown.

RIFF Chunks

RIFF chunks include an additional field in the first 4 bytes of the data field. This additional field provides the form type of the field, which is a four-character code identifying the format of the data stored in the file. A RIFF form is simply a chunk with a chunk ID of RIFF. For example, waveform audio files (WAVE files) have a form type of WAVE.

LIST Chunks

A LIST chunk contains a list, or ordered sequence, of subchunks. LIST chunks also include an additional field in the first 4 bytes of the data field. This additional field contains the list type of the field, which is a four-character code identifying the contents of the list. For example, a LIST chunk with a list type of INFO can contain ICOP and ICRD chunks providing copyright and creation date information.

If an application recognizes the list type, it should know how to interpret the sequence of subchunks. However, since a LIST chunk may contain only subchunks (after the list type), an application that does not know about a specific list type can still navigate through the sequence of subchunks.

RIFF File Functions

The following MMIO functions enable you to manage RIFF files:

Function Description
mmioFOURCC Converts four characters into a four-character code (FOURCC).
mmioStringToFOURCC Converts a null-terminated string into a four-character code.
mmioCreateChunk Creates a chunk in a RIFF file that was opened by mmioOpen.
mmioAscend Ascends out of a chunk in a RIFF file that was descended into by mmioDescend or created by mmioCreateChunk.
mmioDescend Descends into a RIFF file chunk beginning at the current file position, or searches for a specified chunk.

The MMCKINFO Data Structure

Several multimedia file I/O functions use the MMCKINFO structure to specify and retrieve information about a chunk in a RIFF file. The MMIOOS2.H header file defines the MMCKINFO structure as shown:

typedef struct _MMCKINFO {   /* mmckinfo                               */
  FOURCC      ckid;          /* Chunk id (FOURCC)                      */
  ULONG       ulSize;        /* Chunk size (bytes)                     */
  FOURCC      fccType;       /* FOURCC type (if ckid RIFF or LIST)     */
  ULONG       ulDataOffset;  /* File offset of data portion of chunk   */
  ULONG       ulFlags;       /* MMIO_DIRTY (if new chunk)              */
} MMCKINFO;

Four-Character Codes

A four-character code is a 32-bit quantity representing a sequence of one to four ASCII alphanumeric characters, padded on the right with blank characters. The data type for a four-character code is FOURCC. Use the mmioFOURCC function to convert four characters to a four-character code. For example, to use a four-character code for WAVE:

            FOURCC   fccIOProc;

            fccIOProc = mmioFOURCC( 'W', 'A', 'V', 'E' ) ;

To convert a null-terminated string into a four-character code, use the mmioStringToFOURCC function. The following example also generates a four-character code for WAVE:

            FOURCC fccIOProc;

            fccIOProc = mmioStringToFOURCC("WAVE", 0);

The second parameter in mmioStringToFOURCC specifies options for converting the string to a four-character code. If you specify the MMIO_TOUPPER flag, mmioStringToFOURCC converts all alphabetic characters in the string to uppercase. This is useful when you need to specify a four-character code to identify a custom I/O procedure. (Four-character codes are case-sensitive.)

Creating RIFF Chunks

Use the mmioCreateChunk function to create a new chunk by writing a chunk header at the current position in an open file and then 'descending' into the chunk. (See Descending into a Chunk for further information.) You must specify a pointer to a MMCKINFO structure containing information about the new chunk. You also need to determine which chunk type to create by specifying MMIO_CREATERIFF or MMIO_CREATELIST. The return value is 0 if the chunk is successfully created; otherwise, the return value specifies an error code.

The following code fragment illustrates how to create a new chunk with a chunk ID of RIFF and the form type of WAVE.

HMMIO         hmmio;
MMCKINFO      mmckinfo;

mmckinfo.fccType = mmioFOURCC('W', 'A', 'V', 'E');
mmioCreateChunk(hmmio, mmckinfo, MMIO_CREATERIFF);

If you are creating a RIFF or LIST chunk, you must specify the form type in the fccType field of the MMCKINFO structure. In the previous example, the form type is WAVE.

If you know the size of the data field in the new chunk, set the ckSize field in the MMCKINFO structure when you create the chunk. This value is written to the ckSize field in the new chunk. If this value is not correct when you call mmioAscend to mark the end of the chunk, it is automatically rewritten to reflect the correct size of the data field.

After you create a new chunk using mmioCreateChunk, the file position is set to the data field of the chunk (8 bytes from the beginning of the chunk). If the chunk is a RIFF or LIST chunk, the file position is set to the location following the form type or list type (12 bytes from the beginning of the chunk). The ckSize field is assumed to be a 'proposed chunk size' if it turns out to be correct (if you write that much data into the chunk before calling mmioAscend to end the chunk, the mmioAscend will not have to seek back and correct the chunk header.)

Moving between Chunks

RIFF files may consist of nested chunks of information. MMIO services include two functions you can use to move between chunks in a RIFF file: mmioAscend and mmioDescend. You might think of these functions as high-level seek functions. When you descend into a chunk, the file position is set to the data field of the chunk (8 bytes from the beginning of the chunk). For RIFF and LIST chunks, the file position is set to the location following the form type or list type (12 bytes from the beginning of the chunk). When you ascend out of a chunk, the file position is set to the location following the end of the chunk.

Descending into a Chunk

The mmioDescend function descends into a chunk or searches for a chunk, beginning at the current file position. The mmioDescend function requires a pckinfo parameter, which specifies a pointer to a MMCKINFO structure that mmioDescend fills with information on the current chunk. You can also specify the pckinfoParent parameter, which specifies an optional caller-supplied structure that refers to the parent of the chunk that is being searched for. If there is no parent chunk, set pckinfoParent to NULL.

The usFlags parameter specifies options for searching for a chunk. Choose from the following options:

Flag Description
MMIO_FINDCHUNK Searches for a chunk with a specific chunk ID. The ckid field pckinfo should contain the chunk ID of the chunk to search for when mmioDescend is called.
MMIO_FINDRIFF Searches for a chunk with a RIFF chunk ID and with a specific form type. The fccType field of pckinfo should contain the form type of the RIFF chunk to search for when mmioDescend is called.
MMIO_FINDLIST Searches for a chunk with a chunk ID of LIST and with a specific list type. The fccType field of pckinfo should contain the list type of the LIST chunk to search for when mmioDescend is called.

Note: If you do not specify any flags, mmioDescend descends into the chunk that starts at the current file position.

The mmioDescend function fills an MMCKINFO structure with information on the chunk. This information includes the chunk ID (ckid), the size of the data field (ckSize), and the form type, or list type, depending on whether the chunk is a RIFF or LIST chunk. The mmioDescend function assumes that the current file position is the beginning of a chunk header when mmioDescend is called. If pckinfoParent is given, mmioDescend assumes that the current file position is within pckinfoParent (a RIFF or LIST chunk).

Searching for a Chunk

To search for a chunk in an open RIFF file, use mmioDescend with the MMIO_FINDCHUNK parameter. Also set the ckid field of the MMCKINFO structure referenced by pckinfo to the four-character code of the chunk you want to search for.

If you are searching for a RIFF or LIST chunk, the mmioDescend function sets the ckid field of the MMCKINFO structure. Set the fccType field to the four-character code of the form type or list type of the chunk.

Ascending out of a Chunk

After you descend into a chunk and read the data in the chunk, you can move the file position (pointer) to the beginning of the next chunk. This is accomplished by ascending out of the chunk using the mmioAscend function. The mmioAscend function specifies a pointer to an MMCKINFO structure identifying a chunk. The function ascends to the location following the end of this chunk. The return value is 0 if the operation is successful; otherwise, the return value specifies an error code.

If the chunk was descended into using mmioDescend, then mmioAscend seeks to the location following the end of the chunk (past the extra pad byte, if any).

If the chunk was created and descended into using mmioCreateChunk (the MMIO_DIRTY flag in the ulFlags field of pckinfo is set), then the current file position is assumed to mark the end of the data portion of the chunk. If the chunk size is not the same as the value that was stored in ckSize of pckinfo before mmioCreateChunk was called, then mmioAscend seeks back and corrects the chunk size in the chunk header before ascending from the chunk. Also, if the chunk size is odd, then mmioAscend writes a null pad byte at the end of the chunk.

RIFF Compound File Overview

Files based upon the compound file structure contain the following two RIFF chunks at the 'top level' of a RIFF file—(as subchunks of the RIFF chunk):

  • Compound File Resource Group (CGRP) chunk
  • Compound File Table of Contents (CTOC) chunk

The CGRP chunk contains all the compound file elements, concatenated together. An element may be a RIFF file, but it may also be a non-RIFF file, or an arbitrary RIFF chunk, or arbitrary binary data. The definition of the form that contains the CGRP chunk may specify exactly what the elements of the CGRP chunk may be. The CTOC chunk indexes the CGRP chunk, which contains the actual multimedia data elements. Each entry contains the name of the element and other information about the element, including the offset of the element within the CGRP chunk. All the CTOC entries of a table are of the same length and can be specified when the file is created.

The CTOC chunk may appear either before or after the CGRP chunk. Generally, the CTOC chunk is placed at the front of the file to reduce the seek and read times required to access it. See the following figure.

RIFF Compound File Functions

A RIFF compound file can contain multiple file elements. A file element is an individual file that is part of a RIFF compound file. An element of a compound file also could be an entire RIFF file. The MMIO Manager provides service to find, query, and access any file elements in a compound file. It also supports the function of file compaction.

The following MMIO functions enable you to manage RIFF compound files:

Function Description
mmioCFOpen Opens a RIFF compound file by name.
mmioCFClose Closes a RIFF compound file that was opened by mmioCFOpen.
mmioCFGetInfo Retrieves the CTOC header of an open RIFF compound file.
mmioCFSetInfo Modifies information that is stored in the CTOC header of an open RIFF compound file.
mmioCFAddEntry Adds an entry to the CTOC chunk of an open RIFF compound file.
mmioCFChangeEntry Changes a CTOC entry in an open RIFF compound file.
mmioCFFindEntry Finds a CTOC entry in an open RIFF compound file.
mmioCFDeleteEntry Deletes a CTOC entry in an open RIFF compound file.
mmioCFAddElement Adds an element to the CGRP chunk of an open RIFF compound file.
mmioCFCopy Copies the CTOC and CGRP chunks from an open RIFF compound file to another RIFF compound file.
mmioCFCompact Compacts a RIFF compound file by removing elements marked as deleted.
mmioFindElement Enumerates the entries of a compound file.
mmioRemoveElement Removes the specified element in a compound file.

The MMCFINFO Structure

The MMIO Manager uses the MMCFINFO data structure to maintain state information on an open file. The MMCFINFO data structure is defined in the MMIOOS2.H header file as shown:

typedef struct _MMCFINFO  /* mmcfinfo                                    */
 {
 ULONG  ulHeaderSize;     /* CTOC header size                            */
 ULONG  ulEntriesTotal;   /* Num of CTOC table entries                   */
 ULONG  ulEntriesDeleted; /* Num of CTOC table entries to deleted CGRP   */
 ULONG  ulEntriesUnused;  /* Num of unused CTOC entries                  */
 ULONG  ulBytesTotal;     /* Combined byte size of all CGRP elements     */
 ULONG  ulBytesDeleted;   /* Byte size of all deleted CGRP elements      */
 ULONG  ulHeaderFlags;    /* Information about entire compound file (CF) */
 USHORT usEntrySize;      /* Size of each CTOC table entry               */
 USHORT usNameSize;       /* Size of name field in entry, default 13     */
 USHORT usExHdrFields;    /* Num CTOC header extra fields                */
 USHORT usExEntFields;    /* Num CTOC entry extra fields                 */
 } MMCFINFO;

Opening or Creating a RIFF Compound File

To perform I/O procedures on a new or existing RIFF compound file, an application issues the mmioCFOpen function. This function constructs a CTOC in memory for a RIFF compound file. Specify the pmmiocfinfo parameter to identify a pointer to a user-supplied CTOC header structure containing optional header information. The pmmiocfinfo parameter can be NULL if the default values of the fields are sufficient. You can also specify the pmmioinfo parameter, which identifies a pointer to a user-supplied info structure containing optional open information that is passed to mmioOpen.

The ulFlags options for mmioCFOpen include the following:

Flag Description
MMIO_READ Opens a file for reading only (default).
MMIO_WRITE Opens a file for writing only.
MMIO_READWRITE Opens a file for both reading and writing.
MMIO_CREATE Creates a new file.
MMIO_EXCLUSIVE Opens a file with exclusive mode, denying other processes both read and write access to the file.
MMIO_DENYWRITE Opens a file and denies other processes write access to the file.
MMIO_DENYREAD Opens a file and denies other processes read access to the file.
MMIO_DENYNONE Opens a file without denying other processes read or write access to the file.

If the file does not exist, an error is returned unless you specified the MMIO_CREATE option. If the file exists but is not a RIFF compound file, the system returns an error, regardless of whether or not you specified MMIO_CREATE.

The access and sharing flags are maintained only within the set of compound file (CF) functions. If the RIFF compound file or elements are accessed without using the CF functions, the access and sharing modes are unpredictable. An mmioOpen function with a fully qualified element name is considered a CF function since it internally calls mmioCFOpen, thus the flags are predictable in that case.

Closing a RIFF Compound File

Use the mmioCFClose function to close a RIFF compound file that was opened by mmioCFOpen. The mmioCFClose function writes the CTOC back to the RIFF compound file. You can also open an element using the mmioOpen function with the BND and element both specified on the open call (see to Opening or Creating a File). In that case, you would call the mmioClose function, which would close the element and RIFF compound file.

If the process ends, all open elements are closed and the CTOC is rewritten. If the compound file was opened for read only, the CTOC is not rewritten.

If the mmioCFClose fails and you modified CGRP elements, the data stored on the file is inconsistent. Attempt to correct the inconsistency by freeing file space and trying to close the file again.

Retrieving Information

Use the mmioCFGetInfo function to retrieve the CTOC header of an open RIFF compound file. The mmioCFGetInfo function requires a pmmcfinfo parameter which identifies a pointer to a user-supplied buffer that will be filled with the CTOC header. Use the cBytes parameter to specify the size of the pmmcfinfo buffer. This is the maximum number of bytes that will be copied.

The information copied to pmmcfinfo consists of a MMCFINFO structure followed by variable length arrays aulExHdrFldUsage, aulExEntFldUsage, and aulExHdrField.

To find out how large a buffer the user needs to allocate, call mmioCFGetInfo with cBytes equal to the size of a ULONG. This returns the first field of the CTOC header, which happens to be the size of the header. This size can then be used as cBytes on the subsequent call.

Modifying the CTOC Header

Use the mmioCFSetInfo function to modify information that is stored in the CTOC header of an open RIFF compound file. You should only modify the aulExHdrFldUsage and aulExHdrField fields.

The mmioCFSetInfo function requires a pmmcfinfo parameter which identifies a pointer to a user-supplied buffer that contains the modified CTOC header. This buffer was filled in by mmioCFGetInfo and then modified by the user. Use the cBytes parameter to specify the size of the pmmcfinfo buffer. This is the maximum number of bytes that will be copied.

The MMCTOCENTRY Structure

The MMIO Manager uses the MMCTOCENTRY data structure to maintain state information on an open file. The MMCTOCENTRY data structure is defined in the MMIOOS2.H header file as shown:

typedef struct _MMCTOCENTRY  {
 ULONG    ulOffset;          /* Offset of element within CGRP       */
 ULONG    ulSize;            /* Size of element                     */
 ULONG    ulMedType;         /* FOURCC of element                   */
 ULONG    ulMedUsage;        /* Possible sub type                   */
 ULONG    ulCompressTech;    /* Compression technique used          */
 ULONG    ulUncompressBytes; /* Actual size of uncompressed element */
 } MMCTOCENTRY;

Adding an Entry to the CTOC Chunk

Use the mmioCFAddEntry function to add an entry to the CTOC chunk of an open RIFF compound file. (Do not duplicate entries.) The mmioCFAddEntry function requires a pmmctocentry parameter which identifies a pointer to a user-supplied CTOC structure containing the CTOC data. The 'identifier' for the entry is the element name, which is passed in the pmmctocentry buffer. If mmioCFAddEntry expands the current number of entries past the number currently allocated, on a mmioCFClose the CTOC is written following the CGRP in the file.

Changing a CTOC Entry

Use the mmioCFChangeEntry function to modify a CTOC entry in an open RIFF compound file. The mmioCFChangeEntry function requires a pmmctocentry parameter which identifies a pointer to a user-supplied CTOC structure containing modified CTOC data. The 'identifier' for the entry is the element name, which is passed in the pmmctocentry buffer. The mmioCFChangeEntry function updates the CTOC entry with the information contained in the user's pmmctocentry. If you change the compression technique, you must also modify the ulUncompressBytes field. When the compression technique is NULL, the uncompressed bytes field must be the size in bytes of the element when it is uncompressed.

Finding a CTOC Entry

The mmioCFFindEntry function enables you to find a particular entry in an open RIFF compound file. The mmioCFFindEntry function requires the pmmctocentry parameter which identifies a pointer to a user-supplied CTOC structure containing the name of the RIFF compound file element to search for. You can set flags in ulFlags to specify that an element is to be searched for by some attribute other than its name.

Flags for mmioCFFindEntry include the following:

Flag Description
MMIO_FINDFIRST Finds the first entry in the CTOC table.

Note: MMIO_FINDFIRST is ignored if you set either MMIO_FINDDELETED or MMIO_FINDNEXT.

MMIO_FINDNEXT Finds the next entry in the CTOC table after the entry that contains the element searched for.
MMIO_FINDDELETED Finds the first entry in the table that has been marked as 'deleted', or the next deleted entry following the entry that contains the element to search for.

The search is case-insensitive. If no flags are set, the search is for the element only. If the function succeeds, the pmmctocentry buffer is filled with information about the CTOC entry. You can progress through the CTOC entry list by doing a FINDFIRST followed by a series of FINDNEXT, using the information from the previous function.

Deleting a CTOC Entry

Use the mmioCFDeleteEntry function to delete a CTOC entry in an open RIFF compound file. The mmioCFDeleteEntry function requires a pmmctocentry parameter, which identifies a pointer to a user-supplied CTOC structure containing the RIFF compound file element name. The 'identifier' for the entry is the element name, which is passed in the pmmctocentry field. The entry is marked 'deleted' by the FOURCC of FOURCC_DEL. The actual element data remains in place. To physically remove both the entry and the element's data, use the mmioCFCopy function.

Adding an Element to the CGRP Chunk

Use mmioCFAddElement to add an element to the CGRP chunk of an open RIFF compound file. The mmioCFAddElement function requires:

  • A pointer to the name of the element that you want to add to the CGRP chunk (pszElementName)
  • The four-character code of the element (fccType)
  • A pointer to the caller-supplied buffer containing the element data (pchBuffer)
  • The size of the caller-supplied buffer (cchBytes).

The CTOC entry for the element does not have to exist before you call mmioCFAddElement. If the CTOC entry exists, mmioCFChangeEntry modifies its contents. If the CTOC entry does not exist, mmioCFAddEntry is called to add the CTOC entry for this element. The mmioCFAddElement function writes the element to the end of the CGRP chunk. The user's buffer contains the element data.

The CGRP chunk may precede the CTOC chunk and overwrite the CTOC on the file system. This is corrected when the RIFF compound file is closed and the CTOC is rewritten.

Note
You can also add an element to the CGRP chunk by specifying MMIO_CREATE using the mmioOpen function.

Copying CTOC and CGRP Chunks

Use the mmioCFCopy function to copy the CTOC and CGRP chunks from an open RIFF compound file to another RIFF compound file. The mmioCFCopy function requires a pszDestFileName parameter, which identifies the pointer to the name of the destination file.

The mmioCFCopy function opens the destination file for MMIO_CREATE (using mmioOpen) and builds a RIFF BND header at the beginning of the file. The CTOC and CGRP chunks are then copied. The newly written CGRP chunk is compacted; it has no deleted elements.

Compacting RIFF Compound Files

Use the mmioCFCompact function to compact a RIFF compound file in place. The file must not be opened by any other process or the compaction fails. Upon success a new CGRP is written into the same source file with no deleted elements.