QTCapture.c

//////////
//
//  File:       QTCapture.c
//
//  Contains:   Capturing QuickTime movies with the Sequence Grabber.
//              Based on HackTV sample code.
//
//  Written by: Tim Monroe
//
//  Copyright:  © 2001 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <3>      09/13/01    rtm     added video bottleneck code
//     <2>      04/06/01    rtm     added monitor window refresh using SGGrabPict
//     <1>      04/04/01    rtm     first file, using HackTV sources; Carbonized code
//
//////////
 
//////////
//
// header files
//
//////////
 
#include "QTCapture.h"
 
 
//////////
//
// global variables
//
//////////
 
// Sequence Grabber info
SeqGrabComponent                gSeqGrabber = NULL;
SGChannel                       gVideoChannel = 0;
SGChannel                       gSoundChannel = 0;
 
// monitor window info
DialogPtr                       gMonitor = NULL;
PicHandle                       gMonitorPICT = NULL;
Rect                            gActiveVideoRect;
 
// monitor window sizes
Boolean                         gFullSize = false;
Boolean                         gHalfSize = true;
Boolean                         gQuarterSize = false;
 
// state variables
Boolean                         gRecordVideo = true;
Boolean                         gRecordSound = true;
Boolean                         gSplitTracks = false;
 
#if TARGET_OS_MAC
extern                          gAppInForeground;
extern                          gHasNewDialogCalls;
#endif
 
 
//////////
//
// QTCap_Init
// Initialize movie capture.
//
//////////
 
ComponentResult QTCap_Init (void)
{
    ComponentResult             myErr = noErr;
 
    // open the sequence grabber component
    gSeqGrabber = OpenDefaultComponent(SeqGrabComponentType, 0);
    if (gSeqGrabber == NULL) {
        myErr = cantOpenHandler;
        goto bail;
    }
    
    // open the monitor window
    gMonitor = GetNewDialog(kMonitorDLOGID, NULL, (WindowPtr)-1L);
    if (gMonitor == NULL) {
        myErr = memFullErr;
        goto bail;
    }
    
    SetPortDialogPort(gMonitor);
    MacMoveWindow(GetDialogWindow(gMonitor), 10, 30 + GetMBarHeight(), 0);
 
    // initialize the sequence grabber
    myErr = SGInitialize(gSeqGrabber);
    if (myErr == noErr) {
        // configure the sequence grabber component
        myErr = SGSetGWorld(gSeqGrabber, GetDialogPort(gMonitor), NULL);
        if (myErr != noErr)
            goto bail;
        
        // create a video channel
        myErr = SGNewChannel(gSeqGrabber, VideoMediaType, &gVideoChannel);
        if ((gVideoChannel != NULL) && (myErr == noErr)) {
            short       myWidth;
            short       myHeight;
            Rect        myRect;
            
            myErr = SGGetSrcVideoBounds(gVideoChannel, &gActiveVideoRect);
            if (myErr == noErr) {
                myWidth = (gActiveVideoRect.right - gActiveVideoRect.left) / 2;
                myHeight = (gActiveVideoRect.bottom - gActiveVideoRect.top) / 2;
                SizeWindow(GetDialogWindow(gMonitor), myWidth, myHeight, false);
            }
            
            myErr = SGSetChannelUsage(gVideoChannel, seqGrabPreview | seqGrabRecord | seqGrabPlayDuringRecord);
            if (myErr == noErr) {
                GetPortBounds(GetDialogPort(gMonitor), &myRect);
                myErr = SGSetChannelBounds(gVideoChannel, &myRect);
            }
            
            // if an error occurred while configuring video channel, dispose of it
            if (myErr != noErr) {
                SGDisposeChannel(gSeqGrabber, gVideoChannel);
                gVideoChannel = NULL;
            }
        }
        
        // create a sound channel
        myErr = SGNewChannel(gSeqGrabber, SoundMediaType, &gSoundChannel);
        if ((gSoundChannel != NULL) && (myErr == noErr)) {
            Handle      myRates = NULL;
        
            myErr = SGSetChannelUsage(gSoundChannel, seqGrabPreview | seqGrabRecord);
            if (myErr == noErr) {
                // set the volume low to prevent feedback when we start the preview
                // (in case the mic is anywhere near the speaker)
                myErr = SGSetChannelVolume(gSoundChannel, 0x0010);
            }
            
            // add some sample rates to the Sound settings dialog box Rate pop-up menu
            myRates = NewHandleClear(5 * sizeof(Fixed));
            if (myRates != NULL) {
                *((long *)(*myRates) + 0) = Long2Fix(8000);     // add 8kHz rate
                *((long *)(*myRates) + 1) = Long2Fix(11025);    // add 11kHz rate
                *((long *)(*myRates) + 2) = Long2Fix(16000);    // add 16kHz rate
                *((long *)(*myRates) + 3) = Long2Fix(22050);    // add 22kHz rate
                *((long *)(*myRates) + 4) = Long2Fix(32000);    // add 32kHz rate
                SGSetAdditionalSoundRates(gSoundChannel, myRates);
 
                DisposeHandle(myRates);
            }   
 
            // if an error occurred while configuring sound channel, dispose of it
            if (myErr != noErr) {
                SGDisposeChannel(gSeqGrabber, gSoundChannel);
                gSoundChannel = NULL;
            }
        }
    }
    
    // display the monitor window
    MacShowWindow(GetDialogWindow(gMonitor));       
 
    // start previewing
    if (myErr == noErr)
        myErr = SGStartPreview(gSeqGrabber);
    
bail:
    // if an error occurred, clean up
    if (myErr != noErr)
        QTCap_Stop();
    
    return(myErr);
}
 
 
//////////
//
// QTCap_Stop
// Shut down movie capture.
//
//////////
 
void QTCap_Stop (void)
{
    if (gSeqGrabber != NULL) {
        SGStop(gSeqGrabber);
        CloseComponent(gSeqGrabber);
        gSeqGrabber = NULL;
    }   
    
    if (gMonitor != NULL) {
        DisposeDialog(gMonitor);
        gMonitor = NULL;
    }
}
 
 
//////////
//
// QTCap_SetTrackFile
// Prompt the user for the file in which to save a track's data.
//
//////////
 
static ComponentResult QTCap_SetTrackFile (SGChannel theChannel, char *thePrompt, char *theDefaultName)
{
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(thePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(theDefaultName);
    SGOutput                myOutput;
    AliasHandle             myAliasHandle = NULL;
    OSErr                   myErr = noErr;
    
    // prompt the user for new file name
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    myErr = myIsSelected ? noErr : userCanceledErr;
    if (myErr != noErr)
        goto bail;
        
    myErr = QTNewAlias(&myFile, &myAliasHandle, true);
    if (myErr != noErr)
        goto bail;
        
    // create an output from this file
    myErr = SGNewOutput(gSeqGrabber, (Handle)myAliasHandle, rAliasType, seqGrabToDisk, &myOutput);
    if (myErr != noErr)
        goto bail;
 
    // associate this output with the specified channel
    myErr = SGSetChannelOutput(gSeqGrabber, theChannel, myOutput);
 
bail:
    free(myPrompt);
    free(myFileName);
 
    if (myAliasHandle != NULL)
        DisposeHandle((Handle)myAliasHandle);
        
    return(myErr);
}
 
 
//////////
//
// QTCap_Record
// Record the video and audio data into one or more files.
//
//////////
 
void QTCap_Record (void)
{
    FSSpec                      myFile;
    Boolean                     myIsSelected = false;
    Boolean                     myIsReplacing = false;  
    StringPtr                   myPrompt = QTUtils_ConvertCToPascalString(kCapSavePrompt);
    StringPtr                   myFileName = QTUtils_ConvertCToPascalString(kCapSaveMovieFileName);
    long                        myFlags = createMovieFileDontOpenFile | createMovieFileDontCreateMovie | createMovieFileDontCreateResFile;
    ComponentResult             myErr = noErr;
    
    // stop everything while the dialogs are up
    SGStop(gSeqGrabber);
 
    // prompt the user for new file name
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    myErr = myIsSelected ? noErr : userCanceledErr;
    if (myErr != noErr)
        goto bail;
 
    // delete any existing the movie file, if the user so instructs
    if (myIsReplacing)
        DeleteMovieFile(&myFile);
    
    myErr = SGSetDataOutput(gSeqGrabber, &myFile, seqGrabToDisk);
    if (myErr != noErr)
        goto bail;
 
    // ask for separate video and sound track files, if requested
    if ((gSoundChannel != NULL) && gRecordSound && (gVideoChannel != NULL) && gRecordVideo && gSplitTracks) {
        myErr = QTCap_SetTrackFile(gVideoChannel, kVideoSavePrompt, kVideoSaveMovieFileName);
        if (myErr != noErr)
            goto bail;
    
        myErr = QTCap_SetTrackFile(gSoundChannel, kSoundSavePrompt, kSoundSaveMovieFileName);
        if (myErr != noErr)
            goto bail;
    }
 
    // if not recording sound or video, then disable those channels
    if ((gSoundChannel != NULL) && !gRecordSound)
        SGSetChannelUsage(gSoundChannel, 0);
        
    if ((gVideoChannel != NULL) && !gRecordVideo)
        SGSetChannelUsage(gVideoChannel, 0);
 
    // attempt to recover the preview area obscured by dialogs
#if TARGET_OS_WIN32
    UpdatePort(gMonitor);
#endif
    SGUpdate(gSeqGrabber, 0);
 
    // create a movie file for the destination movie
    myErr = CreateMovieFile(&myFile, sigMoviePlayer, smSystemScript, myFlags, NULL, NULL);
    if (myErr != noErr)
        goto bail;
    
    FlushEvents(mDownMask + mUpMask, 0);
    
    // record until the user clicks the mouse button
    myErr = SGStartRecord(gSeqGrabber);
    if (myErr != noErr)
        goto bail;
        
    while (!Button() && (myErr == noErr))
        myErr = SGIdle(gSeqGrabber);
 
    // if we recorded until we ran out of space, then allow SGStop to be called to write the movie resource;
    // the assumption here is that the data output filled up but the disk has enough free space left to
    // write the movie resource
    if (!((myErr == dskFulErr) || (myErr != eofErr)))
        goto bail;
 
    // stop the recording that's currently happening
    myErr = SGStop(gSeqGrabber);
    SGStartPreview(gSeqGrabber);
    
bail:
    free(myPrompt);
    free(myFileName);
 
    if (myErr == noErr)
        return;
        
    SGPause(gSeqGrabber, false);
    SGStartPreview(gSeqGrabber);
}
 
 
//////////
//
// QTCap_SGModalFilterProc
// Handle events while a Sequence Grabber settings dialog box is displayed.
//
// The theRefCon parameter is expected to be the DialogPtr of the monitor window.
//
//////////
 
#if TARGET_OS_MAC
static PASCAL_RTN Boolean QTCap_SGModalFilterProc (DialogPtr theDialog, const EventRecord *theEvent, short *theItemHit, long theRefCon)
{
    Boolean             myEventHandled = false;
    WindowPtr           myWindow = NULL;
    RgnHandle           myWindowRgn = NULL;
    GrafPtr             mySavedPort;
    Rect                myRect;
    DialogPtr           myMonitor = (DialogPtr)theRefCon;
    
    switch (theEvent->what) {
        case updateEvt:
            // find out which window needs to be updated
            myWindow = (WindowPtr)theEvent->message;
            if (myWindow == GetDialogWindow(myMonitor)) {
                // update the monitor window, using the stored picture
                GetPort(&mySavedPort);
                MacSetPort(GetWindowPort(myWindow));
 
#if TARGET_API_MAC_CARBON
                GetPortBounds(GetDialogPort(myMonitor), &myRect);
#else
                myRect = myWindow->portRect;
#endif
 
                // draw the saved monitor picture into the monitor window
                if (gMonitorPICT != NULL)
                    DrawPicture(gMonitorPICT, &myRect);
                    
                // clear the update region
                BeginUpdate(myWindow);
                EndUpdate(myWindow);
                
                MacSetPort(mySavedPort);
                myEventHandled = true;
            } else if ((myWindow != NULL) && (myWindow != GetDialogWindow(theDialog))) {
                // update the specified window, if it's behind the modal dialog box
                QTFrame_HandleEvent((EventRecord *)theEvent);
                myEventHandled = false;     // so sayeth IM
            }
            break;
            
        case nullEvent:
            // do idle-time processing for all open windows in our window list
            if (gAppInForeground) 
                QTFrame_IdleMovieWindows();
 
            myEventHandled = false;
            break;
            
        default:
            myEventHandled = false;
            break;
    }
    
    // let the OS's standard filter proc handle the event, if it hasn't already been handled
    if (gHasNewDialogCalls && (myEventHandled == false))
        myEventHandled = StdFilterProc(theDialog, (EventRecord *)theEvent, theItemHit);
    
    return(myEventHandled);
}
#endif
 
 
//////////
//
// QTCap_GetVideoSettings
// Display the video settings dialog box and get the new settings.
//
//////////
 
void QTCap_GetVideoSettings (void)
{
    Rect                myNewActiveVideoRect;
    short               myWidth, myHeight;
    GrafPtr             mySavedPort;
    SGModalFilterUPP    myFilterUPP = NULL;
    Rect                myRect;
    ComponentResult     myErr = noErr;
    
    // get our current state
    GetPort(&mySavedPort);
    
    // pause previewing
    SGPause(gSeqGrabber, true);
    
    // display the video setting dialog box
    myErr = QTCap_GetChannelSettings(gVideoChannel);
    if (myErr != noErr)
        goto bail;
        
    // retrieve the user's choices
    SGGetSrcVideoBounds(gVideoChannel, &myNewActiveVideoRect);
 
    // set up our port
    SetPortDialogPort(gMonitor);
    
    // has our active rectangle changed?
    // if so, it's because our video standard changed (e.g., NTSC to PAL) and we need to adjust our monitor window
    if (!MacEqualRect(&gActiveVideoRect, &myNewActiveVideoRect)) {
        short           myDivisor = 1;      // assume gFullSize
        
        if (gQuarterSize)
            myDivisor = 4;
        else if (gHalfSize)
            myDivisor = 2;
            
        myWidth = (myNewActiveVideoRect.right - myNewActiveVideoRect.left) / myDivisor;
        myHeight = (myNewActiveVideoRect.bottom - myNewActiveVideoRect.top) / myDivisor;
        
        gActiveVideoRect = myNewActiveVideoRect;
        SizeWindow(GetDialogWindow(gMonitor), myWidth, myHeight, false);
        
        GetPortBounds(GetDialogPort(gMonitor), &myRect);
        SGSetChannelBounds(gVideoChannel, &myRect);
    }
 
bail:
    MacSetPort(mySavedPort);
    
#if !TARGET_OS_MAC
    // this is necessary, for now, to get the grab to start again after the dialog goes away;
    // for some reason the video destRect never gets reset to point back to the monitor window
    SGSetChannelBounds(gVideoChannel, &(gMonitor->portRect));
#endif
 
    // restart previewing
    SGPause(gSeqGrabber, false);
}
 
 
//////////
//
// QTCap_GetSoundSettings
// Display the sound settings dialog box.
//
//////////
 
void QTCap_GetSoundSettings (void)
{
    QTCap_GetChannelSettings(gSoundChannel);
}
 
 
//////////
//
// QTCap_GetChannelSettings
// Display the settings dialog box for the specified channel.
//
//////////
 
static ComponentResult QTCap_GetChannelSettings (SGChannel theChannel)
{
    SGModalFilterUPP    myFilterUPP = NULL;
    ComponentResult     myErr = noErr;
 
    // get rid of any existing monitor picture
    if (gMonitorPICT != NULL) {
        KillPicture(gMonitorPICT);
        gMonitorPICT = NULL;
    }
    
    // get the picture currently in the monitor window
    SGGrabPict(gSeqGrabber, &gMonitorPICT, NULL, 0, grabPictOffScreen);
    
    // display the settings dialog box
#if TARGET_OS_MAC
    myFilterUPP = NewSGModalFilterUPP(QTCap_SGModalFilterProc);
#endif
 
    myErr = SGSettingsDialog(gSeqGrabber, theChannel, 0, NULL, 0L, myFilterUPP, (long)gMonitor);
    
#if TARGET_OS_MAC
    DisposeSGModalFilterUPP(myFilterUPP);
#endif
    
    // get rid of the monitor picture
    if (gMonitorPICT != NULL) {
        KillPicture(gMonitorPICT);
        gMonitorPICT = NULL;
    }
        
    return(myErr);
}
 
 
//////////
//
// QTCap_ResizeMonitorWindow
// Resize the monitor window to the specified size.
//
//////////
 
void QTCap_ResizeMonitorWindow (short theDivisor)
{
    Rect                myRect;
    short               myWidth, myHeight;
    GrafPtr             mySavedPort;
    ComponentResult     myErr = noErr;
 
    // calculate the new width and height
    myWidth = (gActiveVideoRect.right - gActiveVideoRect.left) / theDivisor;
    myHeight = (gActiveVideoRect.bottom - gActiveVideoRect.top) / theDivisor;
    
    gQuarterSize = (theDivisor == 4);
    gHalfSize = (theDivisor == 2);
    gFullSize = (theDivisor == 1);
    
    // resize the monitor window
    GetPort(&mySavedPort);
    SetPortDialogPort(gMonitor);
    
    SGPause(gSeqGrabber, true);
    
    SizeWindow(GetDialogWindow(gMonitor), myWidth, myHeight, false);
    
    GetPortBounds(GetDialogPort(gMonitor), &myRect);
    SGSetChannelBounds(gVideoChannel, &myRect);
 
    MacSetPort(mySavedPort);
    SGPause(gSeqGrabber, false);
}