Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Relevant replacement documents include:
- http://developer.apple.com/documentation/QuickTime/Conceptual/QTKitCaptureProgrammingGuide/Introduction/Introduction.html
- http://developer.apple.com/samplecode/MYRecorder/index.html#//apple_ref/doc/uid/DTS10004263
- http://developer.apple.com/samplecode/QTCompressionOptionsWindow/index.html#//apple_ref/doc/uid/DTS10004627
- http://developer.apple.com/samplecode/StillMotion/index.html#//apple_ref/doc/uid/DTS10004355
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); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14