QTFrameStepper.c

//////////
//
//  File:       QTFrameStepper.c
//
//  Contains:   Functions to step frame-by-frame through a QuickTime movie.
//
//  Written by: Tim Monroe
//              Parts based on DTSQTUtilities.c by Apple Developer Technical Support
//              and on SimpleInMovies sample code by Guillermo A. Ortiz.
//
//  Copyright:  © 1997 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <5>      11/18/98    rtm     added QTStep_GetFrameCount, for illustrative purposes only
//     <4>      03/09/98    rtm     changed nextTimeMediaSample flag to nextTimeStep (to support MPEG files)
//     <3>      01/05/98    rtm     revised and augmented comments
//     <2>      01/02/98    rtm     some code clean-up
//     <1>      12/22/97    rtm     first file
//
//  This file defines functions that you can use to step frame-by-frame through a QuickTime movie.
//  Indeed, it illustrates *two* different methods for doing this: (1) using Movie Toolbox functions
//  to advance (or retreat) to interesting times in the movie; and (2) using movie controller actions
//  to step forward or backward through a movie. To my knowledge, there are no particular advantages
//  to using one or the other method, except that the second method is (as you will see) quite a bit
//  simpler to code.
//
//  METHOD ONE: Use Movie Toolbox calls to step to interesting times in the movie. An interesting time
//  is a time value in a movie, track, or media that meets certain search conditions that you specify.
//  We'll use a very simple search condition: locate the next (or previous) sample in the movie's media.
//  Once we have an interesting time, we display the sample at that time by calling SetMovieTimeValue.
//  To implement this first method, we define three functions (which all operate on an open movie):
//
//  -> QTStep_GoToNextVideoSample: display the sample that follows the current sample in a movie
//  -> QTStep_GoToPrevVideoSample: display the sample that precedes the current sample in a movie
//  -> QTStep_GoToFirstVideoSample: display the first video sample in a movie
//
//  Internally, these functions depend on three static functions defined at the beginning of this file.
//  The code here is extremely straightforward. The only "gotcha" concerns finding the first
//  interesting time in a movie. See the description of QTStep_GetStartTimeOfFirstVideoSample for
//  details.
//
//  METHOD TWO: Use movie controller actions to step through frames in the movie. This method uses
//  the MCDoAction function with the mcActionStep and mcActionGoToTime actions. Using this method,
//  the code is considerably simpler. To implement this second method, we define three functions
//  (which all operate on a movie controller that is associated with an open movie):
//
//  -> QTStep_MCGoToNextVideoSample: display the sample that follows the current sample in a movie
//  -> QTStep_MCGoToPrevVideoSample: display the sample that precedes the current sample in a movie
//  -> QTStep_MCGoToFirstVideoSample: display the first video sample in a movie
//
//  Historical note: Method One is based loosely on sample-stepping code in the DTSQTUtilities package
//  developed by Apple Macintosh Developer Technical Support. Method Two is based on a few functions
//  in the SimpleInMovies sample code written by Guillermo A. Ortiz.
//
//////////
 
#include "QTFrameStepper.h"
 
 
//////////
//
// METHOD ONE: Use Movie Toolbox calls to step to interesting times in the movie.
//
//////////
 
//////////
//
// QTStep_GetStartTimeOfFirstVideoSample
// Return, through the theTime parameter, the starting time of the first video sample of the
// specified QuickTime movie.
//
// The "trick" here is to set the nextTimeEdgeOK flag, to indicate that you want to get the
// starting time of the beginning of the movie.
//
// If this function encounters an error, it returns a (bogus) starting time of -1. Note that
// GetMovieNextInterestingTime also returns -1 as a starting time if the search criteria
// specified in the myFlags parameter are not matched by any interesting time in the movie. 
//
//////////
 
static OSErr QTStep_GetStartTimeOfFirstVideoSample (Movie theMovie, TimeValue *theTime)
{
    short           myFlags;
    OSType          myTypes[1];
    
    *theTime = kBogusStartingTime;                          // a bogus starting time
    if (theMovie == NULL)
        return(invalidMovie);
    
    myFlags = nextTimeMediaSample + nextTimeEdgeOK;         // we want the first sample in the movie
    myTypes[0] = VisualMediaCharacteristic;                 // we want video samples
 
    GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, (TimeValue)0, fixed1, theTime, NULL);
    return(GetMoviesError());
}
 
 
//////////
//
// QTStep_DrawVideoSampleAtTime
// Draw the video sample of a QuickTime movie at the specified time.
//
//////////
 
static OSErr QTStep_DrawVideoSampleAtTime (Movie theMovie, TimeValue theTime)
{
    short           myFlags;
    OSErr           myErr = noErr;
    
    if (theMovie == NULL)
        return(invalidMovie);
    
    // make sure that the specified time lies within the movie's temporal bounds
    if ((theTime < 0) || (theTime > GetMovieDuration(theMovie)))
        return(paramErr);
    
    SetMovieTimeValue(theMovie, theTime);
    myErr = GetMoviesError();
    if (myErr != noErr)
        goto bail;
        
    // the following calls to UpdateMovie and MoviesTask are not necessary
    // if you are handling movie controller events in your main event loop
    // (by passing the event to MCIsPlayerEvent); they don't hurt, however.
    
    // redraw the movie immediately by calling UpdateMovie and MoviesTask
    UpdateMovie(theMovie);
    myErr = GetMoviesError();
    if (myErr != noErr)
        goto bail;
        
    MoviesTask(theMovie, 0L);
    myErr = GetMoviesError();
 
bail:
    return(myErr);
}
 
 
//////////
//
// QTStep_DrawVideoSampleNextOrPrev
// Draw the next or previous video sample of a QuickTime movie.
// If theRate is 1, the next video sample is drawn; if theRate is -1, the previous sample is drawn.
//
//////////
 
static OSErr QTStep_DrawVideoSampleNextOrPrev (Movie theMovie, Fixed theRate)
{
    TimeValue       myCurrTime;
    TimeValue       myNextTime;
    short           myFlags;
    OSType          myTypes[1];
    OSErr           myErr = noErr;
    
    if (theMovie == NULL)
        return(invalidMovie);
    
    myFlags = nextTimeStep;                                 // we want the next frame in the movie's media
    myTypes[0] = VisualMediaCharacteristic;                 // we want video samples
    myCurrTime = GetMovieTime(theMovie, NULL);
 
    GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, myCurrTime, theRate, &myNextTime, NULL);
    myErr = GetMoviesError();
    if (myErr != noErr)
        return(myErr);
        
    myErr = QTStep_DrawVideoSampleAtTime(theMovie, myNextTime);
    
    return(myErr);
}
 
 
//////////
//
// QTStep_GoToFirstVideoSample
// Draw the first video sample of a QuickTime movie.
//
//////////
 
OSErr QTStep_GoToFirstVideoSample (Movie theMovie)
{
    TimeValue       myTime;
    OSErr           myErr = noErr;
    
    if (theMovie == NULL)
        return(invalidMovie);
        
    myErr = QTStep_GetStartTimeOfFirstVideoSample(theMovie, &myTime);
    if (myErr != noErr)
        return(myErr);
        
    myErr = QTStep_DrawVideoSampleAtTime(theMovie, myTime);
    return(myErr);
}
 
 
//////////
//
// QTStep_GoToNextVideoSample
// Draw the next video sample of a QuickTime movie.
//
//////////
 
OSErr QTStep_GoToNextVideoSample (Movie theMovie)
{
    return(QTStep_DrawVideoSampleNextOrPrev(theMovie, fixed1));
}
 
 
//////////
//
// QTStep_GoToPrevVideoSample
// Draw the previous video sample of a QuickTime movie.
//
//////////
 
OSErr QTStep_GoToPrevVideoSample (Movie theMovie)
{
    return(QTStep_DrawVideoSampleNextOrPrev(theMovie, FixMul(Long2Fix(-1), fixed1)));
}
 
 
//////////
//
// METHOD TWO: Use movie controller actions to step thru frames in the movie.
//
//////////
 
//////////
//
// QTStep_MCGoToFirstVideoSample
// Draw the first video sample of the QuickTime movie associated with the specified movie controller.
//
//////////
 
OSErr QTStep_MCGoToFirstVideoSample (MovieController theMC)
{
    TimeRecord      myTimeRecord;
    Movie           myMovie = NULL;
    OSErr           myErr = noErr;
    
    if (theMC == NULL)
        return(paramErr);
        
    myMovie = MCGetMovie(theMC);
    if (myMovie == NULL)
        return(paramErr);
    
    myTimeRecord.value.hi = 0;
    myTimeRecord.value.lo = 0;
    myTimeRecord.base = 0;
    myTimeRecord.scale = GetMovieTimeScale(myMovie);
    myErr = GetMoviesError();
    if (myErr != noErr)
        return(myErr);
    
    return(MCDoAction(theMC, mcActionGoToTime, &myTimeRecord));
}
 
 
//////////
//
// QTStep_MCGoToNextVideoSample
// Draw the next video sample of the QuickTime movie associated with the specified movie controller.
//
//////////
 
OSErr QTStep_MCGoToNextVideoSample (MovieController theMC)
{
    short           myStep = 1;             // advance the movie one frame
    
    if (theMC == NULL)
        return(paramErr);
        
    return(MCDoAction(theMC, mcActionStep, (Ptr)myStep));
}
 
 
//////////
//
// QTStep_MCGoToPrevVideoSample
// Draw the previous video sample of the QuickTime movie associated with the specified movie controller.
//
//////////
 
OSErr QTStep_MCGoToPrevVideoSample (MovieController theMC)
{
    short           myStep = -1;            // back the movie up one frame
    
    if (theMC == NULL)
        return(paramErr);
        
    return(MCDoAction(theMC, mcActionStep, (Ptr)myStep));
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Frame utilities.
//
// These functions illustrate some other useful things you might want to do with movie frames;
// they are not used elsewhere in this file.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTStep_GetFrameCount
// Get the number of frames in the specified movie track. We return the value -1 if
// an error occurs and we cannot determine the number of frames in the track.
//
// Based (loosely) on frame-counting code in ConvertToMovie Jr.c.
// 
// We count the frames in the track by stepping through all of its interesting times
// (the places where the track displays a new sample).
//
//////////
 
long QTStep_GetFrameCount (Track theTrack)
{   
    long        myCount = -1;
    short       myFlags;
    TimeValue   myTime = 0;
    
    if (theTrack == NULL)
        goto bail;
        
    // we want to begin with the first frame (sample) in the track
    myFlags = nextTimeMediaSample + nextTimeEdgeOK;
 
    while (myTime >= 0) {
        myCount++;
        
        // look for the next frame in the track; when there are no more frames,
        // myTime is set to -1, so we'll exit the while loop
        GetTrackNextInterestingTime(theTrack, myFlags, myTime, fixed1, &myTime, NULL);
        
        // after the first interesting time, don't include the time we're currently at
        myFlags = nextTimeStep;
    }
 
bail:
    return(myCount);
}