Understanding QuickTime Atoms

This chapter discusses QuickTime atoms, a basic structure for storing information in QuickTime. It also describes the functions used to create, dispose of, read from, and store to QuickTime atom containers. The QT atom is an enhancement of the existing atom data structure. Most QuickTime data structures (movies, tracks, media) are built of atoms. Newer QuickTime data structures (timecode tracks, sprites) are implemented using QT atoms.

Atom Structures and IDs

A QT atom container is a basic memory structure for storing information in QuickTime. You can use a QT atom container to construct arbitrarily complex hierarchical data structures. You can think of a newly-created QT atom container as the root of a tree structure that contains no children. A QT atom container contains QT atoms (Figure 7-1). Each QT atom contains either data or other atoms. If a QT atom contains other atoms, it is a parent atom and the atoms it contains are its child atoms. If a QT atom contains data, it is called a leaf atom.

Figure 7-1  QT atom container with parent and child atoms

Each QT atom has an offset that describes the atom’s position within the QT atom container. In addition, each QT atom has a type and an ID. The atom type describes the kind of information the atom represents. The atom ID is used to differentiate child atoms of the same type with the same parent; an atom’s ID must be unique for a given parent and type. In addition to the atom ID, each atom has a 1-based index that describes its order relative to other child atoms of the same parent. You can uniquely identify a QT atom in three ways:

You can store and retrieve atoms in a QT atom container by index, ID, or both. For example, to use a QT atom container as a dynamic array or tree structure, you can store and retrieve atoms by index. To use a QT atom container as a database, you can store and retrieve atoms by ID. You can also create, store, and retrieve atoms using both ID and index to create an arbitrarily complex, extensible data structure.

Figure 7-2 shows a QT atom container that has two child atoms. The first child atom (offset = 10) is a leaf atom that has an atom type of 'abcd', an ID of 1000, and an index of 1. The second child atom (offset = 20) has an atom type of 'abcd', an ID of 900, and an index of 2. Because the two child atoms have the same type, they must have different IDs. The second child atom is also a parent atom of three atoms.

Figure 7-2  QT atom container example

The first child atom (offset = 30) has an atom type of 'abcd', an ID of 100, and an index of 1. It does not have any children, nor does it have data. The second child atom (offset = 40) has an atom type of 'word', an ID of 100, and an index of 1. The atom has data, so it is a leaf atom. The second atom (offset = 40) has the same ID as the first atom (offset = 30), but a different atom type. The third child atom (offset = 50) has an atom type of 'abcd', an ID of 1000, and an index of 2. Its atom type and ID are the same as that of another atom (offset = 20) with a different parent.

As a developer, you do not need to parse QT atoms yourself. Instead, you can use the QT atom functions to create atom containers, add atoms to and remove atoms from atom containers, search for atoms in atom containers, and retrieve data from atoms in atom containers.

Most QT atom functions take two parameters to specify a particular atom: the atom container that contains the atom and the offset of the atom in the atom container data structure. You obtain an atom’s offset by calling either QTFindChildByID or QTFindChildByIndex. An atom’s offset may be invalidated if the QT atom container that contains it is modified.

When calling any QT atom function for which you specify a parent atom as a parameter, you can pass the constant kParentAtomIsContainer as an atom offset to indicate that the specified parent atom is the atom container itself. For example, you would call the QTFindChildByIndex function and pass kParentAtomIsContainer constant for the parent atom parameter to indicate that the requested child atom is a child of the atom container itself.

Creating and Disposing of Atom Containers

Before you can add atoms to an atom container, you must first create the container by calling QTNewAtomContainer. The code sample shown in Listing 7-1 calls QTNewAtomContainer to create an atom container.

Listing 7-1  Creating a new atom container

QTAtomContainer spriteData;
OSErr err
// create an atom container to hold a sprite's data
err=QTNewAtomContainer (&spriteData);

When you have finished using an atom container, you should dispose of it by calling the QTDisposeAtomContainer function. The sample code shown in Listing 7-2 calls QTDisposeAtomContainer to dispose of the spriteData atom container.

Listing 7-2  Disposing of an atom container

if (spriteData)
    QTDisposeAtomContainer (spriteData);

Creating New Atoms

You can use the QTInsertChild function to create new atoms and insert them in a QT atom container. The QTInsertChild function creates a new child atom for a parent atom. The caller specifies an atom type and atom ID for the new atom. If you specify a value of 0 for the atom ID, QTInsertChild assigns a unique ID to the atom.

QTInsertChild inserts the atom in the parent’s child list at the index specified by the index parameter; any existing atoms at the same index or greater are moved toward the end of the child list. If you specify a value of 0 for the index parameter, QTInsertChild inserts the atom at the end of the child list.

The code sample in Listing 7-3 creates a new QT atom container and calls QTInsertChild to add an atom. The resulting QT atom container is shown in Figure 7-3. The offset value 10 is returned in the firstAtom parameter.

Listing 7-3  Creating a new QT atom container and calling QTInsertChild to add an atom

QTAtom firstAtom;
QTAtomContainer container;
OSErr err
err = QTNewAtomContainer (&container);
if (!err)
    err = QTInsertChild (container, kParentAtomIsContainer, 'abcd',
        1000, 1, 0, nil, &firstAtom);
Figure 7-3  QT atom container after inserting an atom

The following code sample calls QTInsertChild to create a second child atom. Because a value of 1 is specified for the index parameter, the second atom is inserted in front of the first atom in the child list; the index of the first atom is changed to 2. The resulting QT atom container is shown in Figure 7-4.

QTAtom secondAtom;
FailOSErr (QTInsertChild (container, kParentAtomIsContainer, 'abcd',
    2000, 1, 0, nil, &secondAtom));
Figure 7-4  QT atom container after inserting a second atom

You can call the QTFindChildByID function to retrieve the changed offset of the first atom that was inserted, as shown in the following example. In this example, the QTFindChildByID function returns an offset of 20.

firstAtom = QTFindChildByID (container, kParentAtomIsContainer, 'abcd',
    1000, nil);

Listing 7-4 shows how the QTInsertChild function inserts a leaf atom into the atom container sprite. The new leaf atom contains a sprite image index as its data.

Listing 7-4  Inserting a child atom

if ((propertyAtom = QTFindChildByIndex (sprite, kParentAtomIsContainer,
    kSpritePropertyImageIndex, 1, nil)) == 0)
    FailOSErr (QTInsertChild (sprite, kParentAtomIsContainer,
        kSpritePropertyImageIndex, 1, 1, sizeof(short),&imageIndex,
        nil));

Copying Existing Atoms

QuickTime provides several functions for copying existing atoms within an atom container. The QTInsertChildren function inserts a container of atoms as children of a parent atom in another atom container. Figure 7-5 shows two example QT atom containers, A and B.

Figure 7-5  Two QT atom containers, A and B

The following code sample calls QTFindChildByID to retrieve the offset of the atom in container A. Then, the code sample calls the QTInsertChildren function to insert the atoms in container B as children of the atom in container A. Figure 7-6 shows what container A looks like after the atoms from container B have been inserted.

QTAtom targetAtom;
targetAtom = QTFindChildByID (containerA, kParentAtomIsContainer, 'abcd',
    1000, nil);
FailOSErr (QTInsertChildren (containerA, targetAtom, containerB));
Figure 7-6  QT atom container after child atoms have been inserted

In Listing 7-5, the QTInsertChild function inserts a parent atom into the atom container theSample. Then, the code calls QTInsertChildren to insert the container theSprite into the container theSample. The parent atom is newSpriteAtomMovie Data Types.

Listing 7-5  Inserting a container into another container

 
FailOSErr (QTInsertChild (theSample, kParentAtomIsContainer,
    kSpriteAtomType, spriteID, 0, 0, nil, &newSpriteAtom));
FailOSErr (QTInsertChildren (theSample, newSpriteAtom, theSprite));

QuickTime provides three other functions you can use to manipulate atoms in an atom container. The QTReplaceAtom function replaces an atom and its children with a different atom and its children. You can call the QTSwapAtoms function to swap the contents of two atoms in an atom container; after swapping, the ID and index of each atom remains the same. The QTCopyAtom function copies an atom and its children to a new atom container.

Retrieving Atoms From an Atom Container

QuickTime provides functions you can use to retrieve information about the types of a parent atom’s children, to search for a specific atom, and to retrieve a leaf atom’s data.

You can use the QTCountChildrenOfType and QTGetNextChildType functions to retrieve information about the types of an atom’s children. The QTCountChildrenOfType function returns the number of children of a given atom type for a parent atom. The QTGetNextChildType function returns the next atom type in the child list of a parent atom.

You can use the QTFindChildByIndex, QTFindChildByID, and QTNextChildAnyType functions to retrieve an atom. You call the QTFindChildByIndex function to search for and retrieve a parent atom’s child by its type and index within that type.

Listing 7-6 shows the sample code function SetSpriteData, which updates an atom container that describes a sprite. For each property of the sprite that needs to be updated, SetSpriteData calls QTFindChildByIndex to retrieve the appropriate atom from the atom container. If the atom is found, SetSpriteData calls QTSetAtomData to replace the atom’s data with the new value of the property. If the atom is not found, SetSpriteData calls QTInsertChild to add a new atom for the property.

Listing 7-6  Finding a child atom by index

 
OSErr SetSpriteData (QTAtomContainer sprite, Point *location,
    short *visible, short *layer, short *imageIndex)
{
    OSErr err = noErr;
    QTAtom propertyAtom;
 
    // if the sprite's visible property has a new value
    if (visible)
    {
        // retrieve the atom for the visible property --
        // if none exists, insert one
        if ((propertyAtom = QTFindChildByIndex (sprite,
            kParentAtomIsContainer, kSpritePropertyVisible, 1,
            nil)) == 0)
            FailOSErr (QTInsertChild (sprite, kParentAtomIsContainer,
                kSpritePropertyVisible, 1, 1, sizeof(short), visible,
                nil))
 
        // if an atom does exist, update its data
        else
            FailOSErr (QTSetAtomData (sprite, propertyAtom,
                sizeof(short), visible));
    }
 
    // ...
    // handle other sprite properties
    // ...
}

You can call the QTFindChildByID function to search for and retrieve a parent atom’s child by its type and ID. The sample code function AddSpriteToSample, shown in Listing 7-7, adds a sprite, represented by an atom container, to a key sample, represented by another atom container. AddSpriteToSample calls QTFindChildByID to determine whether the atom container theSample contains an atom of type kSpriteAtomType with the ID spriteIDMovie Data Types. If not, AddSpriteToSample calls QTInsertChild to insert an atom with that type and ID. A value of 0 is passed for the index parameter to indicate that the atom should be inserted at the end of the child list. A value of 0 is passed for the dataSize parameter to indicate that the atom does not have any data. Then, AddSpriteToSample calls QTInsertChildren to insert the atoms in the container theSprite as children of the new atom. FailIf and FailOSErr are macros that exit the current function when an error occurs.

Listing 7-7  Finding a child atom by ID

OSErr AddSpriteToSample (QTAtomContainer theSample,
    QTAtomContainer theSprite, short spriteID)
{
    OSErr err = noErr;
    QTAtom newSpriteAtom;
    FailIf (QTFindChildByID (theSample, kParentAtomIsContainer,
        kSpriteAtomType, spriteID, nil), paramErr);
    FailOSErr (QTInsertChild (theSample, kParentAtomIsContainer,
        kSpriteAtomType, spriteID, 0, 0, nil, &newSpriteAtom));
    FailOSErr (QTInsertChildren (theSample, newSpriteAtom, theSprite));
}

Once you have retrieved a child atom, you can call QTNextChildAnyType function to retrieve subsequent children of a parent atom. QTNextChildAnyType returns an offset to the next atom of any type in a parent atom’s child list. This function is useful for iterating through a parent atom’s children quickly.

QuickTime also provides functions for retrieving an atom’s type, ID, and data. You can call QTGetAtomTypeAndID function to retrieve an atom’s type and ID. You can access an atom’s data in one of three ways.

Modifying Atoms

QuickTime provides functions that you can call to modify attributes or data associated with an atom in an atom container. To modify an atom’s ID, you call the function QTSetAtomIDMovie Data Types.

You use the QTSetAtomData function to update the data associated with a leaf atom in an atom container. The QTSetAtomData function replaces a leaf atom’s data with new data. The code sample in Listing 7-8 calls QTFindChildByIndex to determine whether an atom container contains a sprite’s visible property. If so, the sample calls QTSetAtomData to replace the atom’s data with a new visible property.

Listing 7-8  Modifying an atom's data

QTAtom propertyAtom;
// if the atom isn't in the container, add it
if ((propertyAtom = QTFindChildByIndex (sprite, kParentAtomIsContainer,
    kSpritePropertyVisible, 1, nil)) == 0)
    FailOSErr (QTInsertChild (sprite, kParentAtomIsContainer,
        kSpritePropertyVisible, 1, 0, sizeof(short), visible, nil))
// if the atom is in the container, replace its data
else
    FailOSErr (QTSetAtomData (sprite, propertyAtom, sizeof(short),
        visible));

Removing Atoms From an Atom Container

To remove atoms from an atom container, you can use the QTRemoveAtom and QTRemoveChildren functions. The QTRemoveAtom function removes an atom and its children, if any, from a container. The QTRemoveChildren function removes an atom’s children from a container, but does not remove the atom itself. You can also use QTRemoveChildren to remove all the atoms in an atom container. To do so, you should pass the constant kParentAtomIsContainer for the atom parameter.

The code sample shown in Listing 7-9 adds override samples to a sprite track to animate the sprites in the sprite track. The sample and spriteData variables are atom containers. The spriteData atom container contains atoms that describe a single sprite. The sample atom container contains atoms that describes an override sample.

Each iteration of the for loop calls QTRemoveChildren to remove all atoms from both the sample and the spriteData containers. The sample code updates the index of the image to be used for the sprite and the sprite’s location and calls SetSpriteData, which adds the appropriate atoms to the spriteData atom container. Then, the sample code calls AddSpriteToSample to add the spriteData atom container to the sample atom container. Finally, when all the sprites have been updated, the sample code calls AddSpriteSampleToMedia to add the override sample to the sprite track.

Listing 7-9  Removing atoms from a container

QTAtomContainer sample, spriteData;
// ...
// add the sprite key sample
// ...
// add override samples to make the sprites spin and move
for (i = 1; i <= kNumOverrideSamples; i++)
{
    QTRemoveChildren (sample, kParentAtomIsContainer);
    QTRemoveChildren (spriteData, kParentAtomIsContainer);
 
    // ...
    // update the sprite:
    // - update the imageIndex
    // - update the location
    // ...
    // add atoms to spriteData atom container
    SetSpriteData (spriteData, &location, nil, nil, &imageIndex);
    // add the spriteData atom container to sample
    err = AddSpriteToSample (sample, spriteData, 2);
    // ...
    // update other sprites
    // ...
    // add the sample to the media
    err = AddSpriteSampleToMedia (newMedia, sample,
        kSpriteMediaFrameDuration, false);
}

Creating and Modifying QT Atom Containers

The following functions can be used to create and modify QT atom containers:

Retrieving Atoms and Atom Data

This following functions can be used to retrieve QT Atoms and atom data. Each QT atom contains either data or other atoms.

Constants for QT Atom Functions

You can pass the kParentAtomIsContainer constant to QT atom functions that take an atom container and a parent atom as parameters. When passed in place of the parent atom, this constant indicates that the parent atom is the atom container itself.

enum {
    kParentAtomIsContainer  = 0
};

QT Atom

The QTAtom data type represents the offset of an atom within an atom container.

typedef long QTAtom;

QT Atom Type and ID

The QTAtomType data type represents the type of a QT atom. To be valid, a QT atom’s type must have a nonzero value.

typedef long QTAtomType;

The QTAtomID data type represents the ID of a QT atom. To be valid, a QT atom’s ID must have a nonzero value.

typedef long QTAtomID;

QT Atom Container

The QTAtomContainer data type is a handle to a QT atom container. Your application never modifies the contents of a QT atom container directly. Instead, you use the functions provided by QuickTime for creating and manipulating QT atom containers.

typedef Handle QTAtomContainer;