Technical: QuickTime
Advanced Search
Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

Creating Alternate Movies

Dispatch 15

One of the new features of QuickTime 3 is the ability to create Alternate Movies. Alternate Movies allow you to create content that automatically configures itself for the users machine based on some set of selection criteria. The most common example of this is to create different movies for different data rates. This way, a user connected over a 28.8kbps modem would see the version of the movie appropriate for that connection speed, whereas someone with a T1 connection would see the movie appropriate for the faster connection. Connection speed is just one of the selection criteria. Other checks you can use are Gestalt and Component checks.

You use QuickTime data references to describe where to find the alternate movies. In addition, you can specify that one of the alternates is contained in the same file as the Alternate Movie description itself.

Alternate Movies are described using atoms as defined by the QuickTime Movie File Format. At the highest level, the Movie atom contains a Reference Movie atom to describe the alternate choices. Optionally, the Movie atom can also include a movie itself, as shown below by the Movie Header and Track atoms. This movie will be used if an older version of QuickTime (2.x) is used, since previous versions of QuickTime will ignore the Reference Movie atom. This movie can also be one of the alternate movies. In this case, the Data Reference atom in the Reference Movie Descriptor atom will indicate that the movie is self-contained.

The Reference Movie atom is simply a container for the individual Reference Movie Descriptors. There is one descriptor atom for each Alternate Movie you are describing.

That Reference Movie Descriptor atom contains at least a Data Reference atom and, optionally, other atoms to describe the selection criteria to be used for choosing this movie.

As mentioned above, your Reference Movie Descriptor atom must at least contain a Data Reference atom:

enum {
    ReferenceMovieDataRefAID = FOUR_CHAR_CODE('rdrf'),
};
 
struct ReferenceMovieDataRefRecord {
    long             flags;
    OSType           dataRefType;
    long             dataRefSize;
    char             dataRef[1];
};
typedef struct ReferenceMovieDataRefRecord ReferenceMovieDataRefRecord;
 
enum {
    kDataRefIsSelfContained = (1 << 0)
};

The flags field is either 0 or kDataRefIsSelfContained. If 0, then dataRefType, dataRefSize, and dataRef specify the appropriate data reference. The two most common data reference types are 'alis' and 'url ' for Alias and URL references, respectively.

Alias data references are the contents of AliasHandles, returned by NewAlias or NewAliasMinimal. The QuickTime Plug-in is smart enough to convert a relative alias to a relative URL. To do this, you need to use NewAlias -- NewAliasMinimal doesn't create relative URLs. For the fromFile (anchor file for the relative alias), you should pass the FSSpec of the file you are creating.

A URL data reference is a C string (has a terminating NULL character). You can pass absolute URLs (e.g., http://www.apple.com/media/cute.mov) or relative URLs (e.g., ../media/cute.mov). If the movie is loaded from the desktop, the Movie Toolbox will convert the relative URL into a relative Alias. There is a slight advantage in space considerations to using URL data references rather than Alias data references. Alias records are typically over 100 bytes in size, whereas a relative URL data reference can be quite small.

The most common selection criteria for Alternate Movies is connection speed:

enum {
    ReferenceMovieDataRateAID   = FOUR_CHAR_CODE('rmdr'),
};
 
enum {
    kDataRate144ModemRate       = 1400,
    kDataRate288ModemRate       = 2800,
    kDataRateISDNRate           = 5600,
    kDataRateDualISDNRate       = 11200,
    kDataRateT1Rate             = 150000L,
    kDataRateInfiniteRate       = 0x7FFFFFFF
};
 
struct QTAltDataRateRecord {
    long                     flags;      /* currently always 0 */
    long                     dataRate;
};
typedef struct QTAltDataRateRecord QTAltDataRateRecord;

The data rate selector compares the specified rate against the connection speed specified in the QuickTime Control Panel or the Connection Speed options in the QuickTime Plug-in Settings dialog (both panels modify the same setting -- so a change in one affects both). If no movie matches the user's specified setting, then the movie with the highest data rate that is less than the user's setting will be used. If there are no movies with data rates less than the user's setting, then the movie with the lowest data rate will be used.

You can also use Gestalt and Component checks for picking your movie. This can be useful for a movie that requires certain software in order to be used. For example, a movie that requires the presence of the Intel Indeo 3.1 codec could indicate that a component of type 'imdc', subtype 'iv31' was required. A movie that needs QuickTime VR 2.1 or later could indicate a required Gestalt 'qtvv' value of 0x02100000 or higher was needed. If multiple Gestalt or Component checks are present, all must be true in order for the movie to play.

The Gestalt check can require that Gestalt return a certain minimum value, or it can be used to perform a binary AND operation on a bitfield.

enum {
    ReferenceMovieVersionCheckAID = FOUR_CHAR_CODE('rmvc'),
};
 
/* for VersionCheckRecord.checkType*/
enum {
    kVersionCheckMin             = 0,    // val1 is the min. version required
    kVersionCheckMask            = 1     // (gestalt value & val2) == val1
};
 
struct QTAltVersionCheckRecord {
    long                    flags;       /* currently always 0 */
    OSType                  gestaltTag;
    UInt32                  val1;
    UInt32                  val2;
    short                   checkType;
};
typedef struct QTAltVersionCheckRecord QTAltVersionCheckRecord;

To check for the presence of a Component, use:

enum {
    ReferenceMovieComponentCheckAID = FOUR_CHAR_CODE('rmcd'),
};
 
struct QTAltComponentCheckRecord {
    long                    flags;       /* currently always 0 */
    ComponentDescription    cd;
    unsigned long           minVersion;
};
typedef struct QTAltComponentCheckRecord QTAltComponentCheckRecord;

The ComponentDescription specified in cd is passed to FindNextComponent. If one is found, its version is compared to the specified minVersion.

With the above checks, it is possible that two or more movies might meet the selection criteria. For example, an Alternate Movie might reference an MPEG movie and a movie with a Cinepak track, both designed to be played on a T1. If the user didn't have MPEG installed, then the Cinepak movie would be chosen. If the user did have MPEG, however, then either one might be appropriate. In this case, you can use the Quality atom to specify which movie is the preferred one.

enum {
     ReferenceMovieQualityAID = FOUR_CHAR_CODE('rmqu')
};

The contents of the Quality atom is simply a signed long value. The movie with the higher Quality setting will be preferred.

The last topic to cover is how to create an Alternate Movie with a self-contained movie. The trick is to create the Movie atom that contains the Reference Movie atom as well as the Movie Header and Track atoms of the self-contained movie, and have that at the beginning of the file, before the media data. The basic steps are:

  • Create the Reference Movie atom.
  • Calculate the size of the Reference Movie atom.
  • Create a file. Write a 'free' atom the size of the Reference Movie atom.
  • Call FlattenMovieData with the flags flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData
  • Read the Movie atom that FlattenMovieData created and merge it with the Reference Movie atom.
  • Write the new Movie atom back to the beginning of the file (overwriting the free atom and the original Movie atom).

Here's a code snippet that might help:

/*
    Input variables:
        FSSpec outputFSSpec         FSSpec of output file
        Handle refMovieAtomH        Handle containing Reference Movie atom 
                                    (ReferenceMovieRecordAID)
        Movie origMovie             Movie to become the self-contained movie
*/
    short outFref;
    long atom[2];
    Ptr foo;
    long count, refMovieAtomSize;
 
    /*
        create and open output file
    */
    err = FSpCreate(&outputFSSpec, 'TVOD', MovieFileType, 0);
    if (err) goto bail;
    err = FSpOpenDF(&outputFSSpec, fsRdWrPerm, &outFref);
 
    /*
        write free atom to start of file so that FlattenMovieData
        will add the movie resource and media data far enough into
        the file to allow room for our Reference Movie atom
    */
    refMovieAtomSize = count = GetHandleSize(refMovieAtomH);
    foo = NewPtrClear(refMovieAtomSize);
    if ( (err = MemError()) != noErr) goto bail;
    *(long *)foo = refMovieAtomSize;
    *(long *)(foo + sizeof(long)) = FreeAtomType;
    FSWrite(outFref, &count, foo);
    DisposePtr(foo);
    FSClose(outFref);        // close file so FlattenMovieData can open it
 
    /*
        Flatten self-contained movie to output file 
    */
    newMovie = FlattenMovieData(origMovie, 
            flattenAddMovieToDataFork | 
            flattenForceMovieResourceBeforeMovieData, 
            &outputFSSpec, 'TVOD', -1, 0);
    err = GetMoviesError();
    if (err) goto bail;
 
    /*
        Open output file again and read the Movie atom
    */
    err = FSpOpenDF(&outputFSSpec, fsRdWrPerm, &outFref);
    if (err) goto bail;
    SetFPos(outFref, fsFromStart, refMovieAtomSize);    // should put us at
                                                        // 'moov' atom
    count = 8;
    err = FSRead(outFref, &count, &(atom[0]));
    if (err) goto bail;
    if (atom[1] != MovieAID) {
        err = paramErr;            // this should never happen
        goto bail;
    }
    foo = NewPtr(refMovieAtomSize + atom[0]);
    if ( (err = MemError()) != noErr) goto bail;
 
    /*
        Merge the Movie atom that FlattenMovieData created with our
        Reference Movie atom 
    */
    *(long *)foo = refMovieAtomSize + atom[0];        // combined size
    *(long *)(foo + sizeof(long)) = MovieAID;
 
    // copy our Reference Movie atom (before the rest of the Movie atom)
    BlockMoveData(*refMovieAtomH, foo + 2 * sizeof(long), refMovieAtomSize);
    // read original Movie atom
    count = atom[0] - (sizeof(long) * 2);
    err = FSRead(outFref, &count, foo + 2 * sizeof(long) + refMovieAtomSize);
    if (err) goto bail;
 
    /*
        Write final Movie atom to disk
    */
    SetFPos(outFref, fsFromStart, 0);        // position to start of file
    count = refMovieAtomSize + atom[0];      // combined size
    FSWrite(outFref, &count, foo);
    DisposePtr(foo);
    FSClose(outFref);

 

See Also

Inside Macintosh: Movie Toolbox
QuickTime 3 Reference - Movie Toolbox
QuickTime 3 Reference - QuickTime File Format

Change History

5/10/98 - mdd - First published
Topics
Previous | Next