QuickTime Atoms and Atom Containers

QuickTime stores most of its data using specialized structures in memory, called atoms. Movies themselves are atoms, as are tracks, media, and data samples. There are two kinds of atoms: chunk atoms, which your code accesses by offsets, and QT atoms, for which QuickTime provides a full set of access tools.

Each atom carries its own size and type information as well as its data. A container atom is an atom that contains other atoms, including other container atoms. There are several advantages to using QT atoms for holding and passing information:

Each atom has a four-character type designation that describes its internal structure. For example, movie atoms are type 'moov', while the track atoms inside them are type 'trak'.

Atoms that contain only data, and not other atoms, are called leaf atoms. A leaf atom simply contains a series of data fields accessible by offsets. You can use QuickTime’s atom tools to search through QT atom hierarchies until you get to leaf atoms, then read the leaf atom’s data from its various fields. With chunk atoms, you read their size bytes and access their contents by calculating offsets. For more information about atoms and atom containers, see the book QuickTime File Format. Atoms are also discussed in the QuickTime API Reference.

Figure 8-1 shows an example of the atom structure of a simple QuickTime movie that has one track containing video data. Both the atoms in Figure 8-1 are chunk atoms, so you create and read them through your own code.

Figure 8-1  Atom structure of a simple QuickTime movie
Atom structure of a simple QuickTime movie

QT Atom Containers

A QuickTime atom container is a basic structure for storing information in QuickTime. An atom container is a tree-structured hierarchy of QT atoms. 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, as shown in Figure 8-2. 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. Each parent’s child atom is uniquely identified by its atom type and atom ID. A QT atom that contains data is called a leaf atom.

Figure 8-2  QT atom container with parent and child atoms
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 with the same atom type. You can uniquely identify a QT atom in one of 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 8-3 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 8-3  A QT atom container with two child atoms
A QT atom container with two child atoms

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 = 10) with a different parent.

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, Copying, 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 8-1 calls QTNewAtomContainer to create an atom container.

Listing 8-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 8-2 calls QTDisposeAtomContainer to dispose of the spriteData atom container.

Listing 8-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 8-3 creates a new QT atom container and calls QTInsertChild to add an atom. The resulting QT atom container is shown in Figure 8-4. The offset value 10 is returned in the firstAtom parameter.

Listing 8-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 8-4  QT atom container after inserting an atom
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 8-5.

QTAtom secondAtom;
 
FailOSErr (QTInsertChild (container, kParentAtomIsContainer, 'abcd',
    2000, 1, 0, nil, &secondAtom));
Figure 8-5  QT atom container after inserting a second atom
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 8-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 8-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 8-6 shows two example QT atom containers, A and B.

Figure 8-6  Two QT atom containers, A and B
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 8-7 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 8-7  QT atom container after child atoms have been inserted
QT atom container after child atoms have been inserted

In Listing 8-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 newSpriteAtom.

Listing 8-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 8-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 8-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 8-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 spriteID. 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 8-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 QTSetAtomID.

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 8-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 8-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 8-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 (Listing 8-6), which adds the appropriate atoms to the spriteData atom container. Then, the sample code calls AddSpriteToSample (Listing 8-7) 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 8-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);
}