TweetFollow Us on Twitter

Sep 00 QTToolkit

Volume Number: 16 (2000)
Issue Number: 9
Column Tag: QuickTime Toolkit

The Atomic Cafe

by Tim Monroe

Working With Atoms and Atom Containers

Introduction

In the past two QuickTime Toolkit articles, we've been concerned at least in part with atoms, the basic building-blocks of QuickTime movie files. Atoms are utterly simple in structure (a 4-byte length field, followed by a 4-byte type field, followed by some data), and this utter simplicity means that atoms can be used for a very wide range of tasks. Indeed, the atom-based structure used by QuickTime movie files is so general and so flexible that it has been adopted by the International Standards Organization (ISO) as the basis for the development of a unified digital media storage format for the emerging MPEG-4 specification.

In this article, we're going to continue investigating the basic structure of QuickTime files as sequences of atoms. You might recall that in the previous article we left some unfinished business lying around. Namely, we need to see how to replace an existing atom of a particular type instead of just adding a new atom of that type. It turns out that handling this task in the general case is reasonably difficult, since we can't safely move the movie data atom around in a movie file without doing a lot of work. For the present, we'll be content to see how to amend the QTInfo_MakeFilePreview function we developed last time so that there is at most one 'pnot' atom in a QuickTime movie file.

Because an atom can contain any kind of data whatsoever, it can contain data that consists of one or more other atoms. So, atoms can be arranged hierarchically. We'll take a few moments to consider the hierarchical arrangement of a movie atom (the main repository of bookkeeping information in a QuickTime movie file). Then we'll show how to put our atom-fusing powers to work to create a shortcut movie file, a QuickTime movie file that does nothing more than point to some other QuickTime movie file.

Once we've played with atoms for a while, we're going to shift gears rather abruptly to consider a second kind of atom-based structure, which we'll call an atom container. Atom containers are structures of type QTAtomContainer that are often used inside of QuickTime movie data atoms to store various kinds of information (for example, media samples). They were developed primarily to address some of the shortcomings of atoms. In particular, the Movie Toolbox provides an extensive API for working with atom containers; among other things, this API makes it easy to create and access data arranged hierarchically within atom containers.

We'll get some hands-on experience with atom containers in two ways. First, we'll see how to get and set the user's Internet connection speed preference, which is a piece of information that QuickTime stores internally and happily gives, in the form of an atom container, to anyone who asks. Second, we'll see how to add a movie track to a QuickTime movie. By using movie tracks, we can embed entire QuickTime movies inside of other QuickTime movies. The movie media handler, which manages movie tracks, is one of the most exciting new features of QuickTime 4.1. Once we understand how to work with atom containers, it'll be easy to add movie tracks to an existing QuickTime movie.

Before we begin, though, a word about terminology. As you've been warned, this article is going to discuss two different ways of organizing data, both of which are (for better or worse) called "atoms". The first kind of atom is the one that's used to define the basic structure of QuickTime movie files. The second kind of atom is the one that was introduced in QuickTime 2.1 for storing data in some kinds of media samples (and for other tasks as well); these kinds of atoms are structures of type QTAtom that are stored inside of an atom container.

Some of Apple's QuickTime documentation refers to the first kind of atom as a classic atom (perhaps in the same spirit that one refers to a classic car: it's been around a while) and to the second kind of atom as a QT atom (drawing of course on the data type QTAtom). Some other documentation refers to the first kind of atom as a chunk atom (perhaps because it's just a chunk of data?). I'm not particularly happy with any of these terms, so I'm going to refer to the first kind of atom simply as an atom and to the second kind as an atom container atom. In other words, an atom container atom (of type QTAtom) is always found inside of an atom container (of type QTAtomContainer). Generally, here and in the future, the context will make it clear which kind of atom we're considering, so we can usually get by just talking about atoms.

File Previews: The Sequel

In the previous article ("The Informant" in MacTech, July 2000), we saw how to add a 'pnot' atom to a QuickTime movie file, to create a single-fork movie file with a file preview. (A file preview is the movie clip, image, text, or other data that appears in the preview pane of the file-opening dialog box displayed by a call to StandardGetFilePreview or NavGetFile.) Our strategy was simple: each time the user saves a movie, add a preview atom and (if necessary) a preview data atom to the QuickTime movie file. But we recognized that ultimately we would need to refine this strategy to avoid ending up with multiple preview atoms and preview data atoms. It's time to make some changes to our QTInfo application. In this section, we'll see how to upgrade QTInfo into a nearly-identical application, called QTInfoPlus, that handles file preview atoms correctly.

Removing Existing Previews

In fact, we can solve this little problem by adding a single line of code to the QTInfo_MakeFilePreview function. Immediately after determining that the file reference number passed to QTInfo_MakeFilePreview picks out a data file, we can execute this code:

QTInfo_RemoveAllPreviewsFromFile(theRefNum);

The QTInfo_RemoveAllPreviewsFromFile function looks through the specified open data file and removes any existing preview atoms (that is, atoms of type 'pnot') from that file. In addition, this function removes any preview data atoms referenced by those preview atoms, unless the preview data atoms are of type 'moov'. (We don't want to remove atoms of type 'moov', of course, since they contain essential information about the movie file.) QTInfo_RemoveAllPreviewsFromFile is defined in Listing 1.

Listing 1: Removing all preview atoms from a QuickTime movie file

QTInfo_RemoveAllPreviewsFromFile

OSErr QTInfo_RemoveAllPreviewsFromFile (short theRefNum)
{
   long            myAtomType = 0L;
   short         myAtomIndex = 0;
   short         myCount = 0;
   OSErr         myErr = noErr;

   // count the preview atoms in the file
   myCount = QTInfo_CountAtomsOfTypeInFile(theRefNum, 0L, 
                                       ShowFilePreviewComponentType);
   while (myCount > 0) {
      // get the preview data atom targeted by this preview atom
      myAtomType = ShowFilePreviewComponentType;
      myAtomIndex = myCount;
      myErr = QTInfo_FindPreviewAtomTarget(theRefNum, 
                                          &myAtomType, &myAtomIndex);
         
      // if the preview data atom is the last atom in the file, remove it
      // (unless it's a 'moov' atom)
      if (myErr == noErr)
         if (myAtomType != MovieAID)
            if (QTInfo_IsLastAtomInFile(theRefNum, 
                                       myAtomType, myAtomIndex))
               QTInfo_RemoveAtomFromFile(theRefNum,
                                          myAtomType, myAtomIndex);
   
      // remove or free the preview atom
      if (QTInfo_IsLastAtomInFile(theRefNum, 
                           ShowFilePreviewComponentType, myCount))
         QTInfo_RemoveAtomFromFile(theRefNum,
                           ShowFilePreviewComponentType, myCount);
      else
         QTInfo_FreeAtomInFile(theRefNum,
                        ShowFilePreviewComponentType, myCount);
   
   // if the preview data atom still exists, remove or free it (unless it's a 'moov' atom)
      if (myErr == noErr)
         if (myAtomType != MovieAID)
            if (QTInfo_IsLastAtomInFile(theRefNum,
                                          myAtomType, myAtomIndex))
               QTInfo_RemoveAtomFromFile(theRefNum,
                                          myAtomType, myAtomIndex);
            else
               QTInfo_FreeAtomInFile(theRefNum,
                                          myAtomType, myAtomIndex);

      myCount-;
   }

   return(myErr);
}



As you can see, QTInfo_RemoveAllPreviewsFromFile calls a handful of other functions defined by our application. These other functions do things like count the number of existing preview atoms, find the preview data atom that is the target of a preview atom, determine whether a given atom is the last atom in the file, and so forth.

QTInfo_RemoveAllPreviewsFromFile puts these functions to work like this: for each preview atom in the file (starting with the one nearest the end of the file), find the preview data atom that is referenced by the preview atom. If that target atom isn't a movie atom and it's the last atom in the file, remove it from the file. Then, if the preview atom is the last atom in the file, remove it as well. If the preview atom isn't the last atom in the file, then change it into a free atom (that is, an atom whose type is FreeAtomType). By changing the atom type, we're converting the preview atom into a block of unused space at its current location in the movie file. QuickTime simply ignores any atoms of type FreeAtomType that it encounters when reading through a movie file.

You might think that we could just remove a preview atom from the file and, if it isn't the last atom in the file, move any following atoms up in the file. This would avoid creating "islands" of unused space in our file, but it would be a dangerous thing to do. That's because some atoms in a QuickTime file reference data in other atoms by storing offsets from the beginning of the file. In general, we want to avoid moving atoms around in a QuickTime movie file. It's safer just to convert any unwanted atoms that are not at the end of the file into free atoms.

Once we've removed a preview atom (if it's the last atom in the file) or converted it into a free atom (if it isn't), we then look once again to see if the preview data atom is the last item in the file. This might happen if the preview data atom originally preceded the preview atom and the preview atom was the last atom in the file. If the preview data atom is now the last atom in the file, it's removed; otherwise, it's converted into a free atom.

The net result of all this is to remove any existing preview and preview data atoms from the file, either by truncating the file to exclude those atoms or by converting them into free atoms. At this point, QTInfo_MakeFilePreview can safely add a new preview atom and (if necessary) a preview data atom to that file. So, when all is said and done, the QuickTime movie file will end up with exactly one preview atom and one preview data atom. In the next few subsections, we'll consider how to define the various QTInfoPlus functions called by QTInfo_RemoveAllPreviewsFromFile.

Finding and Counting Atoms

The most fundamental thing we need to be able to do, when working with a file that's composed of atoms, is find an atom of a specific type and index in that file. For instance, we might need to find the first movie atom, or the third preview atom, or the third 'PICT' atom in the file. So we want to devise a function that takes an atom type and an index and then returns to us the position in the file at which that atom begins, if there is an atom of that type and index in the file. Otherwise, the function should return some error.

This task is reasonably straightforward. All we need to do is start at the beginning of the file (or at some other offset in the file specified by the caller) and inspect the type of the atom at that location. If the desired index is 1 and the desired atom type is the type of that atom, we're done: we've found the desired atom. Otherwise, we need to keep looking. We can find the next atom in the file by moving forward in the file by the size of the atom currently under consideration. We continue inspecting each atom and moving forward in the file until we find the atom of the specified type and index or until we reach the end of the file. Listing 2 defines the function QTInfo_FindAtomOfTypeAndIndexInFile, which is our basic atom-finding tool.

Listing 2: Finding an atom in a QuickTime movie file

QTInfo_FindAtomOfTypeAndIndexInFile

OSErr QTInfo_FindAtomOfTypeAndIndexInFile (short theRefNum, 
            long *theOffset, long theAtomType, short theIndex, 
               long *theDataSize, Ptr *theDataPtr)
{
   short            myIndex = 1;
   long               myFileSize;
   long               myFilePos = 0L;
   long               myAtomHeader[2];
   long               mySize = 0L;
   OSType            myType = 0L;
   Ptr               myDataPtr = NULL;
   Boolean         isAtomFound = false;
   OSErr            myErr = paramErr;
   
   if (theOffset == NULL)
      goto bail;
      
   if (QTInfo_IsRefNumOfResourceFork(theRefNum)) 
      goto bail;
      
   myFilePos = *theOffset;

   // get the total size of the file
   GetEOF(theRefNum, &myFileSize);
      
   while (!isAtomFound) {
      myErr = SetFPos(theRefNum, fsFromStart, myFilePos);
      if (myErr != noErr)
         goto bail;
      
      // read the atom header at the current file position
      mySize = sizeof(myAtomHeader);
      myErr = FSRead(theRefNum, &mySize, myAtomHeader);
      if (myErr != noErr)
         goto bail;

      mySize = EndianU32_BtoN(myAtomHeader[0]);
      myType = EndianU32_BtoN(myAtomHeader[1]);
      
      if ((myIndex == theIndex) && 
                  ((theAtomType == myType) 
                        || (theAtomType == kQTInfoAnyAtomType))) {
         // we found an atom of the specified type and index;
         // return the atom if the caller wants it
         if (theDataPtr != NULL) {
            myDataPtr = NewPtrClear(mySize);
            if (myDataPtr == NULL) {
               myErr = MemError();
               goto bail;
            }
            
            // back up to the beginning of the atom
            myErr = SetFPos(theRefNum, fsFromStart, myFilePos);
            if (myErr != noErr)
               goto bail;
               
            myErr = FSRead(theRefNum, &mySize, myDataPtr);
            if (myErr != noErr)
               goto bail;
         }
      
         isAtomFound = true;
      } else {
      // we haven't found an atom of the specified type and index; keep on looking
         myFilePos += mySize;
         if ((theAtomType == myType) || 
                                 (theAtomType == kQTInfoAnyAtomType))
            myIndex++;

         // make sure we're moving forward in the file, but not too far...
         if ((mySize <= 0) || 
            (myFilePos > (myFileSize - sizeof(myAtomHeader)))) {
            myErr = cannotFindAtomErr;
            goto bail;
         }
      }
   }   // while (!isAtomFound)
   
   // if we got to here, we found the correct atom
   if (theOffset != NULL)
      *theOffset = myFilePos;
      
   if (theDataPtr != NULL)
      *theDataPtr = myDataPtr;
       
   if (theDataSize != NULL)
      *theDataSize = mySize;
      
bail:
   if (myErr != noErr)
      if (myDataPtr != NULL)
         DisposePtr(myDataPtr);

   return(myErr);
}


QTInfo_FindAtomOfTypeAndIndexInFile returns to its caller the offset within the file of the beginning of the atom of the desired type and index. In addition, if the caller passes in non-NULL values in the theDataPtr or theDataSize parameters, QTInfo_FindAtomOfTypeAndIndexInFile returns a copy of the entire atom (including the atom header) or the atom size to the caller. The returned offset, data, and atom size can be used for a variety of purposes. For instance, Listing 3 defines the QTInfo_CountAtomsOfTypeInFile function, which counts the number of atoms of a specific type in a file.

Listing 3: Counting the atoms in a QuickTime movie file

QTInfo_CountAtomsOfTypeInFile

short QTInfo_CountAtomsOfTypeInFile (short theRefNum,
                                       long theOffset, long theAtomType)
{
   short            myIndex = 0;
   long               myFilePos = theOffset;
   long               myAtomSize = 0L;
   OSErr            myErr = noErr;
   
   if (QTInfo_IsRefNumOfResourceFork(theRefNum)) 
      return(myIndex);
      
   while (myErr == noErr) {
      myErr = QTInfo_FindAtomOfTypeAndIndexInFile(theRefNum, 
                  &myFilePos, theAtomType, 1, &myAtomSize, NULL);
      if (myErr == noErr)
         myIndex++;
      myFilePos += myAtomSize;
      
      // avoid an infinite loop...
      if (myAtomSize <= 0)
         break;
   }

   return(myIndex);
}


QTInfo_CountAtomsOfTypeInFile uses the offset and atom size returned by QTInfo_FindAtomOfTypeAndIndexInFile to walk through the file looking for atoms of the specified type.

Similarly, it's easy to use QTInfo_FindAtomOfTypeAndIndexInFile to determine whether a particular atom is the last atom in a file. We simply call QTInfo_FindAtomOfTypeAndIndexInFile to get the offset in the file of the given atom and then call it again to see if there are any atoms of any kind following that atom. Listing 4 defines a function that does precisely this.

Listing 4: Determining whether an atom is the last atom in a file

QTInfo_IsLastAtomInFile

Boolean QTInfo_IsLastAtomInFile (short theRefNum, long theAtomType, short theIndex)

{

   Boolean         isLastAtom = false;

   long               myOffset = 0L;

   long               myAtomSize = 0L;

   OSErr            myErr = noErr;



   // find the offset and size of the atom of the specified type and index in the file

   myErr = QTInfo_FindAtomOfTypeAndIndexInFile(theRefNum, 

         &myOffset, theAtomType, theIndex, &myAtomSize, NULL);

   if (myErr == noErr) {

      // look for an atom of any type following that atom

      myOffset += myAtomSize;

      myErr = QTInfo_FindAtomOfTypeAndIndexInFile(theRefNum, 

                  &myOffset, kQTInfoAnyAtomType, 1, NULL, NULL);

      if (myErr != noErr)

         isLastAtom = true;

   }   



   return(isLastAtom);

}

Finding the Preview Data Atom

Given a preview atom, we sometimes need to know which other atom is the target of that atom. In other words, we want to find the atom that we've been calling the preview data atom - the atom that contains the data for the file preview. This is fairly easy: we just need to read the data in the preview atom, which has the structure of a PreviewResourceRecord record. Listing 5 defines the QTInfo_FindPreviewAtomTarget function, which does this.

Listing 5: Finding the target of a preview atom

QTInfo_FindPreviewAtomTarget

OSErr QTInfo_FindPreviewAtomTarget (short theRefNum,
                                 long *theAtomType, short *theIndex)
{
   long                              myOffset = 0L;
   PreviewResourceRecord      myPNOTRecord;
   long                              mySize;
   OSErr                           myErr = noErr;

   if ((theAtomType == NULL) || (theIndex == NULL))
      return(paramErr);

   // find the offset of the atom of the specified type and index in the file
   myErr = QTInfo_FindAtomOfTypeAndIndexInFile(theRefNum, 
               &myOffset, *theAtomType, *theIndex, NULL, NULL);
   if (myErr == noErr) {
      // set the file mark to the beginning of the atom data
      myErr = SetFPos(theRefNum, fsFromStart,
                                       myOffset + (2 * sizeof(long)));
      if (myErr == noErr) {
         // read the atom data
         mySize = sizeof(myPNOTRecord);
         myErr = FSRead(theRefNum, &mySize, &myPNOTRecord);
         if (myErr == noErr) {
            *theAtomType = EndianU32_BtoN(myPNOTRecord.resType);
            *theIndex = EndianS16_BtoN(myPNOTRecord.resID);
         }
      }
   }

   return(myErr);
}


QTInfo_FindPreviewAtomTarget calls QTInfo_FindAtomOfTypeAndIndexInFile to find the location of the atom whose type and index are passed to it in the theAtomType and theIndex parameters. Then it advances the file mark to the beginning of the atom data and reads the atom data into myPNOTRecord. The type and index of the preview data atom (suitably converted from big-endian to native-endian form) are then returned to the caller.

Removing and Freeing Atoms

It's quite easy to remove an atom from a file or convert it into a free atom. Listing 6 shows how we define the QTInfo_RemoveAtomFromFile function, which removes an atom from a file by truncating the file at the beginning of the atom (by calling SetEOF). Note that any atoms that follow the specified atom are also removed from the file. To avoid any problems, we should always call QTInfo_IsLastAtomInFile to make sure that the atom to be removed from the file is the last atom in the file.

Listing 6: Removing an atom from a file

QTInfo_RemoveAtomFromFile
OSErr QTInfo_RemoveAtomFromFile (short theRefNum,
                                    long theAtomType, short theIndex)
{
   long                  myOffset = 0L;
   long                  myAtomSize = 0L;
   OSErr               myErr = noErr;

   // find the offset of the atom of the specified type and index in the file
   myErr = QTInfo_FindAtomOfTypeAndIndexInFile(theRefNum, 
         &myOffset, theAtomType, theIndex, &myAtomSize, NULL);
   if (myErr == noErr)
      myErr = SetEOF(theRefNum, myOffset);

   return(myErr);
}

It's almost as easy to convert an atom into a free atom. All we need to do is position the file mark to the beginning of the type field in the atom header and write the value 'free' into that field. Listing 7 shows how we do this.

Listing 7: Freeing an atom in a file

QTInfo_FreeAtomInFile

OSErr QTInfo_FreeAtomInFile (short theRefNum, 
                                    long theAtomType, short theIndex)
{
   OSType            myType = EndianU32_NtoB(FreeAtomType);
   long               mySize = sizeof(myType);
   long               myOffset = 0L;
   OSErr            myErr = noErr;

   // find the offset of the atom of the specified type and index in the file
   myErr = QTInfo_FindAtomOfTypeAndIndexInFile(theRefNum, 
                  &myOffset, theAtomType, theIndex, NULL, NULL);
   if (myErr == noErr) {
      // change the atom type to 'free'
      myErr = SetFPos(theRefNum, fsFromStart,
                                             myOffset + sizeof(long));
      if (myErr == noErr)
         myErr = FSWrite(theRefNum, &mySize, &myType);
   }   

   return(myErr);
}

So far, then, we've managed to define a handful of utility functions that allow us to find atoms in files, get the sizes of those atoms, remove atoms from files, count the number of atoms of a specific type in a file, and so forth. These are precisely the functions called by the QTInfo_RemoveAllPreviewsFromFile function, which we're using to make sure that any QuickTime movie file we create or edit has at most one preview resource. It would be easy (and fun) to define an entire library of atom utilities, but we'll have to restrain our programming urges here. We've got other work to do.

Shortcut Movie Files

I mentioned earlier that an atom can contain any kind of data, and in particular it can contain other atoms. That is to say, atoms can be arranged hierarchically. Up to now, however, we've worked with a QuickTime movie file as a mere concatenation of atoms. We've looked at the data of several of those atoms, but we haven't yet met any atoms that contain other atoms. It's time for that to change.

A good example of an atom that contains other atoms is the movie atom itself. A typical movie atom contains a track atom (of type TrackAID) for each track in the QuickTime movie, along with other atoms that contain the movie metadata. A movie atom can also contain a movie user data atom (of type UserDataAID), which contains the movie user data. A track atom, in turn, contains other atoms that define the track characteristics and that point to the media data. And so on, as deep as is necessary to completely characterize a movie and its data.

An atom that contains no other atoms is called a leaf atom. A leaf atom may or may not actually contain any data. Typically a leaf atom does contain data, but it's possible that the very presence in the file of the atom has significance. In that case, the leaf atom consists solely of the 8-byte atom header. An atom that contains one or more other atoms is called a container atom. A movie atom is a container atom. By contrast, a preview atom is a leaf atom, since it contains data but no other atoms. (A preview atom points to or references another atom, but it does not contain it.)

Let's build a container atom. Now, it's beyond our current capabilities to build a typical movie atom, with all its complicated subatoms and subsubatoms. But there is a kind of QuickTime movie file that consists entirely of a movie atom and which is simple enough for us to build; this kind of file is called a shortcut movie file. A shortcut movie file is a movie file that picks out a single other movie file. It's rather like an alias file in the Macintosh file system or a shortcut on Windows. Opening a shortcut movie file using the Movie Toolbox function OpenMovieFile causes QuickTime to look for the file that is referred to by the shortcut movie file; if that target file can be found, then OpenMovieFile opens it and returns to the caller a file reference number for that target file.

Shortcut movie files provide a cross-platform mechanism for referring to QuickTime movie files. They can be useful in all the same ways that alias files (on Mac) or shortcuts (on Windows) can be useful, at least when working with QuickTime movies. For instance, a web page might contain an embedded URL to a shortcut movie file. If the webmaster wants to update the movie displayed in the web page, he or she needs only to create a new shortcut movie file that refers to the updated movie and then put the new shortcut movie file in the location occupied by the previous shortcut movie file. In this way, the contents of the web page can be changed without altering the actual HTML tags of the page.

QuickTime has supported shortcut movie files since version 3.0. QuickTime version 4.0 introduced the Movie Toolbox function CreateShortcutMovieFile, which we can use to create a shortcut movie file. But the structure of shortcut movie files is so simple that we can build them ourselves. Let's see how to do this.

The format of shortcut movie files is (to my knowledge) currently undocumented, but it's quite simple: the shortcut movie file consists entirely of a single movie atom, which in turn contains a movie data reference alias atom (of type MovieDataRefAliasAID). This atom contains a single data reference atom (of type DataRefAID). Finally, the data reference atom is a leaf atom that contains the type of the data reference followed immediately by the data reference itself. Figure 1 shows the general structure of a shortcut movie file.


Figure 1. The structure of a shortcut movie file.

It's easy enough to create a file that has this structure. The only thing we don't yet know is what a data reference is, to put into the data reference atom. In the next QuickTime Toolkit article, we'll take a long look at data references; for the moment, we'll just suppose that a suitable data reference is passed to us. (To look ahead a bit, a data reference is a handle to some data that picks out a movie file or other file. For instance, a URL data reference is a handle to the NULL-terminated string of characters in the URL.) Listing 8 shows the function QTShortCut_CreateShortcutMovieFile that takes that data reference and data reference type and then builds a shortcut movie file.

Listing 8: Creating a shortcut movie file

QTShortCut_CreateShortcutMovieFile

OSErr QTShortCut_CreateShortcutMovieFile (Handle theDataRef, 
                  OSType theDataRefType, FSSpecPtr theFSSpecPtr)
{
   long            myVersion = 0L;
   OSErr         myErr = noErr;
   
   myErr = Gestalt(gestaltQuickTime, &myVersion);
   if (myErr != noErr)
      goto bail;

   if (((myVersion >> 16) & 0xffff) >= 0x0400) {
      // we're running under QuickTime 4.0 or greater
      myErr = CreateShortcutMovieFile(theFSSpecPtr,
                              sigMoviePlayer,
                              smCurrentScript,
                              createMovieFileDeleteCurFile | 
                                    createMovieFileDontCreateResFile,
                              theDataRef,
                              theDataRefType);
                              
   } else {
      // we're running under a version of QuickTime prior to 4.0
      OSType                     myDataRefType;
      unsigned long         myAtomHeaderSize;      
      Ptr                        myData = NULL;
      Handle                     myAtom = NULL;

      // create the atom data that goes into a data reference atom (we will create this 
      // atom's header when we create the movie atom that contains it); the atom data is 
      // the data reference type followed by the data reference itself
      myDataRefType = EndianU32_NtoB(theDataRefType);
      myAtomHeaderSize = 2 * sizeof(long);      

      // allocate a data block and copy the data reference type and data reference into it
      myData = NewPtrClear(sizeof(OSType) + 
                                 GetHandleSize(theDataRef));
      if (myData == NULL)
         goto bail;
            
      BlockMove(&myDataRefType, myData, sizeof(OSType));
      BlockMove(*theDataRef, (Ptr)(myData + sizeof(OSType)), 
                                             GetHandleSize(theDataRef));

      // create a handle to contain the size and type fields of the movie atom, as well as
      // the size and type fields of the movie data reference alias atom contained in it
      // and of the data reference atom contained in the movie data reference alias atom
      myAtom = NewHandleClear(3 * myAtomHeaderSize);
      if (myAtom == NULL)
         goto bail;
      
      // fill in the size and type fields of the three atoms
      *((long *)(*myAtom + 0x00)) = EndianU32_NtoB((3 * 
                           myAtomHeaderSize) + GetPtrSize(myData));
      *((long *)(*myAtom + 0x04)) = EndianU32_NtoB(MovieAID);
      *((long *)(*myAtom + 0x08)) = EndianU32_NtoB((2 * 
                           myAtomHeaderSize) + GetPtrSize(myData));
      *((long *)(*myAtom + 0x0C)) = 
                              EndianU32_NtoB(MovieDataRefAliasAID);
      *((long *)(*myAtom + 0x10)) = EndianU32_NtoB((1 * 
                           myAtomHeaderSize) + GetPtrSize(myData));
   *((long *)(*myAtom + 0x14)) = EndianU32_NtoB(DataRefAID);

      // concatenate the data in myData onto the end of the movie atom
      myErr = PtrAndHand(myData, myAtom, GetPtrSize(myData));
      if (myErr != noErr)
         goto bail;

      // create the shortcut movie file
      myErr = QTShortCut_WriteHandleToFile(myAtom, 
                                                            theFSSpecPtr);
bail:
      if (myData != NULL)   
         DisposePtr(myData);

      if (myAtom!= NULL)
         DisposeHandle(myAtom);
   }

   return(myErr);
}


Our function QTShortCut_CreateShortcutMovieFile calls the Movie Toolbox function CreateShortcutMovieFile if it's available; otherwise it creates the movie atom itself, by building up three consecutive atom headers and then appending the data reference type and data onto the end of those atom headers. It writes the entire movie atom into the specified file by calling the QTShortCut_WriteHandleToFile function. (We've already encountered a version of this function, called QTDX_WriteHandleToFile; see "In and Out" in MacTech, May 2000.)

Atom Containers

QuickTime version 2.1 introduced a new way to store information that greatly facilitates creating hierarchical collections of data and retrieving data from those collections. The basic ideas are very simple: at the root of the data hierarchy is an object called an atom container. Inside of an atom container are other objects, called atoms. (If we need to distinguish these atoms from the ones we've been considering up to now, we'll call them atom container atoms.) An atom can contain other atoms, in which case it is a parent atom. The atoms that are contained within a parent atom are called its child atoms. If an atom contains only data (and no other atoms), it is a leaf atom. The data in a leaf atom is always in big-endian format. (Well, almost always; we'll encounter an exception to this rule in a little while.)

An atom has an atom type and an atom ID. A parent atom can contain any number of children of any type and ID. The only restriction is that, for a given parent, no two children can have the same type and ID. So we can uniquely identify an atom by specifying its parent, its type, and its ID. (For an atom that is contained directly in the atom container, the atom container is considered to be the atom's parent; the special constant kParentAtomIsContainer is used to signal this fact.) We can also identify a particular atom by specifying its parent, its type, and an index of atoms of that type in that parent. (QuickTime supports yet a third method of identifying atoms, using the atom's position in the atom container, called its offset; we won't consider this way of identifying atoms here.)

Let's consider a few examples. Figure 2 shows a very simple collection of data, where the atom container has just two children, each of which holds a long integer that represents the length (in millimeters) of a lizard. These leaf atoms both have the atom type 'lzln'.


Figure 2. A simple atom container.

Figure 3 shows a more complicated arrangement of data. In this case, the root atom container contains two parent atoms, both of type 'ldat' (for "lizard data"). Within each 'ldat' atom are two children, which have different types. The atom of type 'lzln' contains a long integer (as in Figure 2) and the atom of type 'lnam' (for "lizard name") contains a string of characters. (This is neither a C string nor a Pascal string; it's just the characters themselves.) Notice that both 'lzln' atoms have an atom ID of 1; this is okay, since those atoms have different parents.


Figure 3. A more complex atom container.

Atom containers can be vastly more complicated than the ones shown in Figures 2 and 3, and they don't have to exhibit the kind of nice symmetry we see there. On the other hand, some real-life atom containers are just that simple. But no matter how complicated they are, we'll use the same functions to build atoms and atom containers and to retrieve data from atom containers. Let's see how to accomplish these tasks.

Creating Atom Containers

The file Movies.h defines these types for working with atoms and atom containers:

typedef Handle                     QTAtomContainer;
typedef long                        QTAtom;
typedef long                        QTAtomType;
typedef long                        QTAtomID;

Notice that an atom container is just a handle to some data (structured in a specific way, to be sure). This means that we can determine the size of an atom container by using the function GetHandleSize. That's about as much as we need to know about the way an atom container is stored in memory. The actual structure of an atom container is publicly documented, but thankfully we will not need to learn anything about that structure. The Movie Toolbox provides all the functions we'll need in order to create and use atoms and atom containers.

We create an atom container by calling the QTNewAtomContainer function, like this:

QTAtomContainer          myAtomContainer = NULL;
myErr = QTNewAtomContainer(&myAtomContainer);

If QTNewAtomContainer completes successfully, then the value of the variable myAtomContainer is a new, empty atom container. We can then add atoms to that container by calling QTInsertChild. For instance, to add the two children shown in Figure 2 to this atom container, we could execute this code:

myLong = EndianU32_NtoB(1029);
myErr = QTInsertChild(   myAtomContainer, 
                                 kParentAtomIsContainer, 
                           kLizardLength, 1, 0,
                           sizeof(myLong), &myLong, NULL);
myLong = EndianU32_NtoB(1253);
myErr = QTInsertChild(   myAtomContainer, 
                                 kParentAtomIsContainer, 
                           kLizardLength, 2, 0, 
                           sizeof(myLong), &myLong, NULL);

The second parameter to the QTInsertChild function specifies the parent atom of the child we're inserting. Here, you'll notice, we're using the constant kParentAtomIsContainer to indicate that the parent atom is the atom container itself. The third and fourth parameters specify the type and ID of the new atom. The fifth parameter is the desired index of the new atom within the parent atom; we don't care about the index here, so we pass the value 0 to indicate that the new atom is to be inserted as the last child of the specified type in the parent atom.

The sixth and seventh parameters to QTInsertChild specify the number of bytes of data to be added to the atom, along with a pointer to the atom data itself. The last parameter is a pointer to a variable of type QTAtom, in which QTInsertChild will return to us an identifier for the new atom; we don't need that information here, so we pass NULL in that parameter.

We can create a hierarchy within an atom container by inserting parent atoms and then adding some children to those parents. We also call QTInsertChild to insert a parent atom, but we do not need to specify any data or data size; instead, we specify a variable of type QTAtom in which the identifier of the new parent atom is returned to us. Here's an example:

QTAtom                        myLizardAtom;
myErr = QTInsertChild(   myAtomContainer, 
                                 kParentAtomIsContainer, 
                           kLizardData, 1, 1, 0, NULL, 
                           &myLizardAtom);

Then we can insert a child atom into this parent atom, like this:

myErr = QTInsertChild(   myAtomContainer, 
                                 myLizardAtom, 
                           kLizardName, 1, 1, 
                           strlen(theLizardName), 
                           theLizardName, NULL);

Note that the second parameter here is the parent atom that we just created. If we insert another atom (this time of type 'lzln') into the parent and then repeat the whole process for the second lizard, we'd have the atom structure shown in Figure 3.

Finding Atoms in Atom Containers

If we are given an atom container, it's almost as easy to get data out of it as it is to put data into it. First we need to find the atom whose data we want. The standard way to do this is to start at the top of the hierarchy and gradually descend until we find the parent of the desired atom. Then we can get an atom identifier of the target atom by calling the QTFindChildByID function. For example, if myLizardAtom is the parent atom for the atoms that hold the data about our lizard Avril, then we can get the name atom by executing this code:

myNameAtom = QTFindChildByID(myAtomContainer, myLizardAtom, 
                              kLizardName, 1, NULL);

QTFindChildByID actually inspects both the type and ID passed to it (not just the ID, as the name might suggest).

The Movie Toolbox provides a number of other functions that are useful for finding specific atoms, including QTCountChildrenOfType, QTFindChildByIndex, QTGetNextChildType, and QTNextChildAnyType.

Getting Atom Data

Once we've found a leaf atom, we can get the data from that atom in several ways. If we want a copy of the atom data that will persist even after we've disposed of the atom container, we can call QTCopyAtomDataToHandle or QTCopyAtomDataToPtr, passing in a handle or pointer to a block of memory that's big enough to hold the leaf atom data. If, on the other hand, we just want to look at the atom data and don't need a copy of it, we can call the QTGetAtomDataPtr function, which returns a pointer to the actual leaf atom data. If you plan to make calls that might move memory, then you should call QTLockContainer before calling QTGetAtomDataPtr; then call QTUnlockContainer when you are done with the data pointer.

If we want to retrieve our lizard's name, we could make this call:

QTGetAtomDataPtr(myAtomContainer, myNameAtom, &myNameSize, amp;myNameData);

If QTGetAtomDataPtr completes successfully, then myNameData points to the string of characters that make up the name, and myNameSize contains the size of that name.

Internet Connection Speed

For our first real-life encounter with atom containers, let's consider how to get and set the user's Internet connection speed preference. The user can set a preference in the Connection Speed panel of the QuickTime[TM] Settings control panel, shown in Figure 4.


Figure 4. The Connection Speed panel.

QuickTime uses this setting for various purposes. For instance, if a user wants to play an alternate data rate movie file located on a remote server, QuickTime uses this connection speed to select the correct target movie. (An alternate data rate movie file is a movie file that references other movies, each tailored for downloading across a connection of a certain speed.)

We can retrieve the user's current QuickTime preferences by calling the GetQuickTimePreference function, like this:

myErr = GetQuickTimePreference(ConnectionSpeedPrefsType, 
                                    &myPrefsContainer);

The first parameter specifies the kind of preference we wish to retrieve, and the second parameter is the address of an atom container in which the requested preference data is returned. It's up to us to dispose of that atom container when we are done reading data from it. In the present case, when we retrieve the Internet connection speed, the atom container contains an atom of type ConnectionSpeedPrefsType whose data is structured as a record of type ConnectionSpeedPrefsRecord, defined like this:

struct ConnectionSpeedPrefsRecord {
   long                              connectionSpeed;
};

This record contains a single field that indicates the number of bytes per second that the user's Internet connection can support. The file MoviesFormat.h defines a set of common values:

enum {
   kDataRate144ModemRate            = 1400,
   kDataRate288ModemRate            = 2800,
   kDataRateISDNRate                  = 5600,
   kDataRateDualISDNRate            = 11200,
   kDataRateT1Rate                     = 150000L,
   kDataRateInfiniteRate            = 0x7FFFFFFF
};

Once we've received an atom container from GetQuickTimePreference, we can use the QTFindChildByID function to find the child atom of type ConnectionSpeedPrefsType. Then we get the atom data by calling the QTGetAtomDataPtr function. Finally, we can read the value stored in the connectionSpeed field, to find the current connection speed preference. We'll return this value as the function result, whether or not it's one of the predefined common values. If any error occurs, however, we'll return the value kDataRate288ModemRate, which is a reasonable default. Listing 9 shows the complete function QTUtils_GetUsersConnectionSpeed.

Listing 9: Getting the user's Internet connection speed preference

QTUtils_GetUsersConnectionSpeed

long QTUtils_GetUsersConnectionSpeed (void)
{
   QTAtomContainer                        myPrefsContainer = NULL;
   QTAtom                                    myPrefsAtom = 0;
   ConnectionSpeedPrefsRecord         myPrefsRec;
   long                                       myDataSize = 0L;
   long                                       mySpeed = 
                                             kDataRate288ModemRate;
   Ptr                                       myAtomData = NULL;
   OSErr                                    myErr = noErr;

   myErr = GetQuickTimePreference(ConnectionSpeedPrefsType, 
                                    &myPrefsContainer);
   if (myErr == noErr) {
      // find the atom of the desired type
      myPrefsAtom = QTFindChildByID(myPrefsContainer, 
                              kParentAtomIsContainer, 
                              ConnectionSpeedPrefsType, 1, NULL);
      if (myPrefsAtom != 0) {
         // read the data contained in that atom and verify that the data is of the
         // size we are expecting
         QTGetAtomDataPtr(myPrefsContainer, myPrefsAtom, 
                                             &myDataSize, &myAtomData);
         if (myDataSize == sizeof(ConnectionSpeedPrefsRecord)) {
            // read the connection speed
            myPrefsRec = 
                        *(ConnectionSpeedPrefsRecord *)myAtomData;
            mySpeed = myPrefsRec.connectionSpeed;
         }
      }

      QTDisposeAtomContainer(myPrefsContainer);
   }
   
   return(mySpeed);
}


Note that we haven't performed any endian-swapping on the value we read from the connection speed preferences record. That's because the data in this particular atom container is stored in its native-endian format. This is an exception to the general rule that data in atom containers is big-endian. A user's QuickTime preferences are not designed to be moved from machine to machine, so there is no need to enforce big-endian byte ordering.

Listing 10 shows how we can set a user's Internet connection speed. In general, we should let the user decide the connection speed preference, but it can sometimes be useful to do this programmatically.

Listing 10: Setting the user's Internet connection speed preference

QTUtils_SetUsersConnectionSpeed

OSErr QTUtils_SetUsersConnectionSpeed (long theSpeed)
{
   QTAtomContainer                           myPrefsContainer = NULL;
   ConnectionSpeedPrefsRecord            myPrefsRec;
   OSErr                                       myErr = noErr;

   myErr = QTNewAtomContainer(&myPrefsContainer);
   if (myErr == noErr) {
      myPrefsRec.connectionSpeed = theSpeed;
      
      myErr = QTInsertChild(myPrefsContainer, 
                              kParentAtomIsContainer, 
                              ConnectionSpeedPrefsType, 1, 0, 
                              sizeof(ConnectionSpeedPrefsRecord), 
                              &myPrefsRec, NULL);
      if (myErr == noErr)
         myErr = SetQuickTimePreference(
                     ConnectionSpeedPrefsType, myPrefsContainer);
         
      QTDisposeAtomContainer(myPrefsContainer);
   }

   return(myErr);
}


QTUtils_SetUsersConnectionSpeed creates a new atom container, inserts a single child atom into the container that holds the desired speed, and then passes that container to the SetQuickTimePreference function. Once SetQuickTimePreference returns, we can safely call QTDisposeAtomContainer to dispose of the atom container we created.

Movie Tracks

You might recall that in an earlier article ("Opening The Toolbox" in MacTech, March 2000), we saw how to write an application that plays one QuickTime movie inside of another QuickTime movie. The embedded movie (what we then called the "picture-in-picture movie") could have looping characteristics different from those of the main movie. For example, the embedded movie could keep looping over and over while the main movie played though once and then stopped. And, in theory, the embedded movie could play at twice its normal speed while the main movie played at, say, half its normal speed. (We didn't actually provide this alternate speed capability, but it would be easy enough to add.)

The only drawback to this was that we needed the special playback application QTMooVToolbox to make it all happen. Wouldn't it be nice if we could create movie files with these capabilities, so that they would play back using any QuickTime-savvy application? This is precisely what's offered in QuickTime 4.1 with the introduction of movie tracks managed by the movie media handler. By adding movie tracks to an existing QuickTime movie, we can effectively embed an entire QuickTime movie into that movie. (This capability is sometimes called the movie-in-movie capability; the embedded movie is also called the child movie, while the main movie is also called the parent movie.) Figure 5 shows one movie embedded within another movie using a movie track.


Figure 5. A child movie inside of a parent movie.

Remember that the looping characteristics and playback rate of a movie are associated with the movie's time base. Prior to QuickTime 4.1, it was possible to create movies with overlaid video tracks, but all the tracks in the movie shared the same time base. The time base of the overlaid track is slaved to that of the other tracks. What movie tracks bring to the table is the ability to have non-slaved time bases in a single movie. That is to say, each child movie can have its own time base, resulting in looping and playback rate characteristics independent of those of the parent movie.

Adding a Movie Track to a Movie

So let's see how to create movie tracks. Suppose that theWindowObject is a window object for an open QuickTime movie file and that theDataRef and theDataRefType are a data reference and a data reference type for some other QuickTime movie file. Then we can call the QTMIM_AddMovieTrack function defined in Listing 11 to add to that open movie a movie track that references that file. (Once again, we'll postpone discussing data references until the next article; for now, all we need to know is that they pick out QuickTime movie files, either on the local machine or elsewhere on the Internet.)

Listing 11: Adding a movie track to a QuickTime movie

QTMIM_AddMovieTrack

OSErr QTMIM_AddMovieTrack (WindowObject theWindowObject, 
                           OSType theDataRefType, Handle theDataRef)
{
   Movie            myMovie = NULL;         // the parent movie
   Track            myTrack = NULL;         // the movie track
   Media            myMedia = NULL;         // the movie track's media
   OSErr            myErr = paramErr;
   
   if ((theWindowObject == NULL) || 
                                                (theDataRef == NULL))
      goto bail;
      
   myMovie = (**theWindowObject).fMovie;
      
   // create the movie track and media
   myTrack = NewMovieTrack(myMovie, 
                                 FixRatio(kChildMovieWidth, 1), 
                                 FixRatio(kChildMovieHeight, 1), 
                                       kFullVolume);
   myErr = GetMoviesError();
   if (myErr != noErr)
      goto bail;
      
   myMedia = NewTrackMedia(myTrack, MovieMediaType, 
                                       kMovieTimeScale, NULL, 0);
   myErr = GetMoviesError();
   if (myErr != noErr)
      goto bail;

   // create the media sample(s)
   myErr = BeginMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;

   myErr = QTMIM_AddMovieTrackSampleToMedia(theWindowObject, 
                     myMedia, theDataRefType, theDataRef);
   if (myErr != noErr)
      goto bail;

   myErr = EndMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;
   
   // add the media to the track
   myErr = InsertMediaIntoTrack(myTrack, 0, 0, 
                           GetMediaDuration(myMedia), fixed1);

bail:
   return(myErr);
}


There is absolutely nothing new about this function. It's virtually identical to the function QTMM_CreateVideoMovie that we encountered in an earlier article (see "Making Movies" in MacTech, June 2000). The only real difference is that we've created a media of type MovieMediaType; also, here we call QTMIM_AddMovieTrackSampleToMedia to add media samples to the new track, while earlier we called QTMM_AddVideoSamplesToMedia.

Creating a Movie Track Media Sample

By now you might be wondering what this has to do with atom containers. The answer is simple: the media sample for a movie track consists of an atom container whose atoms specify the movie to be embedded in the main movie, as well as some of the playback characteristics of the embedded movie. In other words, the function QTMIM_AddMovieTrackSampleToMedia needs only to create an appropriate atom container and pass that container to the AddMediaSample function.

We'll begin therefore by calling QTNewAtomContainer to create a new atom container; since this container will serve as our media sample, we'll call it mySample:

myErr = QTNewAtomContainer(&mySample);

Into this new atom container we want to put an atom of type kMovieMediaDataReference, whose data consists of the data reference type and the data reference of the movie file that is to be the embedded movie. We can create the atom data like this:

myData = NewPtrClear(sizeof(OSType) + 
                                    GetHandleSize(theDataRef));
myType = EndianU32_NtoB(theDataRefType);
BlockMove(&myType, myData, sizeof(OSType));
BlockMove(*theDataRef, myData + sizeof(OSType), 
                                       GetHandleSize(theDataRef));

Then we can insert the atom into the atom container by calling QTInsertChild:

myErr = QTInsertChild(mySample, kParentAtomIsContainer, 
                              kMovieMediaDataReference, 1, 1, 
                           GetPtrSize(myData), myData, NULL);

At this point, we could call AddMediaSample to add the atom container mySample as the single media sample of the movie track. But we'd like the embedded movie to start playing automatically when the parent movie reaches the start time of the movie track, which is not the default behavior. To have the embedded movie automatically start playing, we need to add another atom to the atom container, of type kMovieMediaAutoPlay.

myBoolean = true;
myErr = QTInsertChild(mySample, kParentAtomIsContainer, 
                              kMovieMediaAutoPlay, 1, 1, 
                     sizeof(myBoolean), &myBoolean, NULL);

Now we can create a sample description and add the atom container to the movie track media. Listing 12 shows our function QTMIM_AddMovieTrackSampleToMedia for adding a media sample to a movie track.

Listing 12: Adding a sample to the movie track media

QTMIM_AddMovieTrackSampleToMedia

OSErr QTMIM_AddMovieTrackSampleToMedia 
         (WindowObject theWindowObject, Media theMedia, 
               OSType theDataRefType, Handle theDataRef)
{
#pragma unused(theWindowObject)
   QTAtomContainer                     mySample = NULL;
   QTAtom                                    myRegionAtom;
   SampleDescriptionHandle         myImageDesc = NULL;
   Ptr                                          myData = NULL;
   OSType                                    myType;
   Boolean                                    myBoolean;
   OSErr                                       myErr = paramErr;
   
   // create a new atom container to hold the sample data
   myErr = QTNewAtomContainer(&mySample);
   if (myErr != noErr)

      goto bail;

   // concatenate the data reference type and data reference 
   // into a single block of data
   myData = NewPtrClear(sizeof(OSType) + 
                                    GetHandleSize(theDataRef));
   if (myData == NULL)

      goto bail;
   
   // convert the data to big-endian format
   myType = EndianU32_NtoB(theDataRefType);
   
   BlockMove(&myType, myData, sizeof(OSType));
   BlockMove(*theDataRef, myData + sizeof(OSType), 
                                    GetHandleSize(theDataRef));
   
   // add an atom of type kMovieMediaDataReference 
   // to the atom container
   myErr = QTInsertChild(mySample,
                                 kParentAtomIsContainer, 

                           kMovieMediaDataReference, 1, 1, 
                           GetPtrSize(myData), myData, NULL);
   if (myErr != noErr)
      goto bail;

   // add an auto-start atom

   myBoolean = true;
   myErr = QTInsertChild
                           (mySample, kParentAtomIsContainer, 
                        kMovieMediaAutoPlay, 1, 1, 
                     sizeof(myBoolean), &myBoolean, NULL);
   if (myErr != noErr)

      goto bail;
   
   // create a sample description
   myImageDesc = (SampleDescriptionHandle)
               NewHandleClear(sizeof(SampleDescription));
   if (myImageDesc == NULL)

      goto bail;

   (**myImageDesc).descSize = sizeof(SampleDescription);
   (**myImageDesc).dataFormat = MovieMediaType;

   myErr = AddMediaSample(   
               theMedia, 
               mySample,
               0,
               GetHandleSize((Handle)mySample), 
   GetMovieDuration(GetTrackMovie(GetMediaTrack
                        (theMedia))),                  myImageDesc,
               1,   
               0,   

               NULL);

bail:

   if (myData != NULL)
      DisposePtr(myData);

   if (myImageDesc != NULL)
      DisposeHandle((Handle)myImageDesc);

   if (mySample != NULL)
      QTDisposeAtomContainer(mySample);

   return(myErr);
}


Notice that, as promised earlier, we call GetHandleSize to get the size of the atom container when we call AddMediaSample.

This Month's Code

This month, our sample code is scattered across four different sets of files. The updated version of QTInfo is called QTInfoPlus and includes a new version of the QTInfo_MakeFilePreview function and all the utilities that we used to access the atoms in a QuickTime movie file. The code for creating shortcut movie files is contained in the snippet QTShortcut.c. The code for getting and setting the user's Internet connection speed preference is contained in the file QTUtilities.c (which has been part of every project we've developed so far). Finally, the code for adding movie tracks to QuickTime movies is contained in the project for the sample application QTMovieTrack. QTMovieTrack allows the user to configure a large number of settings for the new movie track, in addition to the auto-playback setting (which we considered earlier). Figure 6 shows the dialog box that QTMovieTrack displays to allow the user to configure a new movie track.


Figure 6. QTMovieTrack's Movie Track Properties dialog box.

For a complete explanation of the slaving and scaling options illustrated in Figure 6, see the document "QuickTime 4.1", downloadable in PDF form from the location <http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/rmWhatsnewQT.htm>.

Conclusion

QuickTime tries very hard to insulate us from having to work directly with the kinds of atoms that comprise movie files (the so-called "classic" or "chunk" atoms). The Movie Toolbox provides an extensive set of high-level routines that we can use to create new movie files, open existing movie files, edit movie files, and so forth. Nonetheless, we've seen that there are occasions when we do need to interact with atoms directly. A good example of this concerns adding a file preview to a single-fork movie file. QuickTime currently provides no API to do this, so we are forced to work with the file data - the atoms - directly. Similarly, if we want to create a shortcut movie file on a machine that's running a version of QuickTime prior to 4.0, we need to work with atoms.

By contrast, we'll encounter atom containers and their associated atom container atoms at virtually every step we take forward in our journey through QuickTime. Atom containers and their children provide an easy way to maintain hierarchical data, and they're backed by an extensive programming interface. So atom containers are now the repository of choice for storing and exchanging data. We'll see them used in media samples, tween tracks, input maps, musical instruments, wired actions, video effect tracks, and for a large number of other uses. In other words, Internet connection speed preferences and movie tracks are just the tip of the ice floe.

Credits

The functions for getting and setting a user's Internet connection speed preferences are based on code by Mike Dodd in the Letter from the Ice Floe, Dispatch 17 (found at <http://developer.apple.com/quicktime/icefloe/dispatch017.html>. Thanks are due to Ken Doyle for reviewing this article and offering some helpful comments.


Tim Monroe recently acquired 3 pet lizards (green anoles), not realizing that he'd need to feed them things like crickets, mealworms, and spiders. His house is now surprisingly spider-free, however. You can send your bugs to him at monroe@apple.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for up to $150 off Apple’s new MSRP, starting at only $849. Free 1-2 day delivery is available to most US... Read more
M2 Mac minis on sale for $100-$200 off MSRP,...
B&H Photo has Apple’s M2-powered Mac minis back in stock and on sale today for $100-$200 off MSRP. Free 1-2 day shipping is available for most US addresses: – Mac mini M2/256GB SSD: $499, save $... Read more
Mac Studios with M2 Max and M2 Ultra CPUs on...
B&H Photo has standard-configuration Mac Studios with Apple’s M2 Max & Ultra CPUs in stock today and on Easter sale for $200 off MSRP. Their prices are the lowest available for these models... Read more
Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more

Jobs Board

Senior Product Associate - *Apple* Pay (AME...
…is seeking a Senior Associate of Digital Product Management to support our Apple Pay product team. Labs drives innovation at American Express by originating, Read more
Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.