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:
QT atoms can nest indefinitely, forming hierarchies that are easy to pass from one process to another.
QuickTime provides a single set of tools by which you can search and manipulate QT atoms of all types.
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.
QT Atom Containers
Creating, Copying, and Disposing of Atom Containers
Creating New Atoms
Copying Existing Atoms
Retrieving Atoms From an Atom Container
Modifying Atoms
Removing Atoms From an Atom Container
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.
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:
by its offset within its QT atom container
by its parent atom, type, and index
by its parent atom, type, and ID
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.
Warning: Since QT atoms are offsets into a data structure, they can be changed during editing operations on QT atom containers, such as inserting or deleting atoms. For a given atom, editing child atoms is safe, but editing sibling or parent atoms invalidates that atom’s offset. s
Note: For cross-platform purposes, all data in a QT atom is expected to be in big-endian format. However, leaf data can be little-endian if it is custom to an application.
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.
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.
Note: You do not need to parse QT atoms. Instead, the QT atom functions can be used 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.
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); |
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); |
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)); |
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)); |
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.
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)); |
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.
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.
To copy an atom’s data to a handle, you can use the QTCopyAtomDataToHandle
function.
To copy an atom’s data to a pointer, you can use the QTCopyAtomDataToPtr
function.
To access an atom’s data directly, you should lock the atom container in memory by calling QTLockContainer
. Once the container is locked, you can call QTGetAtomDataPtr
to retrieve a pointer to an atom’s data. When you have finished accessing the atom’s data, you should call the QTUnlockContainer
function to unlock the container in memory.
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)); |
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); |
} |
© 2003, 2002 Apple Computer, Inc. All Rights Reserved. (Last updated: 2002-10-01)