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.
Munggrab.c
/* |
File: Munggrab.c |
Description: This example shows how to run the Sequence Grabber in record mode and use |
a DataProc to get and modify the captured data. Munggrab calculates the |
frame rate using the time value stamp passed to the data proc then draws this |
rate onto the frame. This technique provides optimal performance, far better |
than using preview mode or bottlenecks. This code will help a lot when |
capturing from DV and should allow 30fps playthrough using DV capture on a G3. |
Author: km, era |
Copyright: © Copyright 2000 - 2002 Apple Computer, Inc. All rights reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): <3> 3/28/02 DV source rect bug fix in DataProc |
<2> 7/08/01 carbonized |
<1> 1/13/00 initial release |
*/ |
// NOTE: |
// this sample uses carbon accessesors and will not |
// build if you have not specified a carbon target |
// build for carbon |
#define TARGET_API_MAC_CARBON 1 |
#if __APPLE_CC__ |
#include <Carbon/Carbon.h> |
#include <QuickTime/QuickTime.h> |
#else |
#include <ConditionalMacros.h> |
#include <Carbon.h> |
#include <QuickTimeComponents.h> |
#include <stdio.h> |
#endif |
// defines |
#define BailErr(x) {err = x; if(err != noErr) goto bail;} |
// mung data struct |
typedef struct { |
WindowRef pWindow; // window |
Rect boundsRect; // bounds rect |
GWorldPtr pGWorld; // offscreen |
SeqGrabComponent seqGrab; // sequence grabber |
ImageSequence decomSeq; // unique identifier for our decompression sequence |
ImageSequence drawSeq; // unique identifier for our draw sequence |
long drawSize; |
TimeValue lastTime; |
TimeScale timeScale; |
long frameCount; |
} MungDataRecord, *MungDataPtr; |
// globals |
static BitMap gScreenbits; |
static MungDataPtr gMungData = NULL; |
static Boolean gDone = false, |
gIsCollapsed = false, |
gIsGrabbing = false; |
void Initialize(void); |
OSErr InitializeMungData(Rect inBounds, WindowRef inWindow); |
OSErr MakeAWindow(WindowRef *outWindow); |
SeqGrabComponent MakeSequenceGrabber(WindowRef pWindow); |
OSErr MakeSequenceGrabChannel(SeqGrabComponent seqGrab, SGChannel *sgchanVideo, Rect const *rect); |
void DoUpdate(void); |
OSErr MakeImageSequenceForGWorld(GWorldPtr pGWorld, GWorldPtr pDest, long *imageSize, ImageSequence *seq); |
pascal OSErr MungGrabDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon); |
// -------------------- |
// Initialize for Carbon & QuickTime |
// |
void Initialize(void) |
{ |
InitCursor(); |
EnterMovies(); |
GetQDGlobalsScreenBits(&gScreenbits); |
} |
// -------------------- |
// InitializeMungData |
// |
OSErr InitializeMungData(Rect inBounds, WindowRef inWindow) |
{ |
CGrafPtr theOldPort; |
GDHandle theOldDevice; |
OSErr err = noErr; |
// allocate memory for the data |
gMungData = (MungDataPtr)NewPtrClear(sizeof(MungDataRecord)); |
if (MemError() || NULL == gMungData ) return NULL; |
// create a GWorld |
err = QTNewGWorld(&(gMungData->pGWorld), // returned GWorld |
k32ARGBPixelFormat, // pixel format |
&inBounds, // bounds |
0, // color table |
NULL, // GDHandle |
0); // flags |
BailErr(err); |
// lock the pixmap and make sure it's locked because |
// we can't decompress into an unlocked pixmap |
if(!LockPixels(GetGWorldPixMap(gMungData->pGWorld))) |
goto bail; |
GetGWorld(&theOldPort, &theOldDevice); |
SetGWorld(gMungData->pGWorld, NULL); |
BackColor(blackColor); |
ForeColor(whiteColor); |
EraseRect(&inBounds); |
SetGWorld(theOldPort, theOldDevice); |
gMungData->boundsRect = inBounds; |
gMungData->pWindow = inWindow; |
bail: |
return err; |
} |
// -------------------- |
// MakeImageSequenceForGWorld |
// |
OSErr MakeImageSequenceForGWorld(GWorldPtr pGWorld, GWorldPtr pDest, long *imageSize, ImageSequence *seq) |
{ |
ImageDescriptionHandle desc = NULL; |
PixMapHandle hPixMap = GetGWorldPixMap(pGWorld); |
Rect bounds; |
OSErr err = noErr; |
GetPixBounds(hPixMap, &bounds); |
*seq = NULL; |
// returns an image description for the GWorlds PixMap |
// on entry the imageDesc is NULL, on return it is correctly filled out |
// you are responsible for disposing it |
err = MakeImageDescriptionForPixMap(hPixMap, &desc); |
BailErr(err); |
*imageSize = (GetPixRowBytes(hPixMap) * (*desc)->height); // ((**hPixMap).rowBytes & 0x3fff) * (*desc)->height; |
// begin the process of decompressing a sequence of frames |
// the destination is the onscreen window |
err = DecompressSequenceBegin(seq, // pointer to field to receive unique ID for sequence |
desc, // handle to image description structure |
pDest, // port for the DESTINATION image |
NULL, // graphics device handle, if port is set, set to NULL |
&bounds, // source rectangle defining the portion of the image to decompress |
NULL, // transformation matrix |
ditherCopy, // transfer mode specifier |
(RgnHandle)NULL, // clipping region in dest. coordinate system to use as a mask |
0, // flags |
codecNormalQuality, // accuracy in decompression |
anyCodec); // compressor identifier or special identifiers ie. bestSpeedCodec |
bail: |
if (desc) |
DisposeHandle((Handle)desc); |
return err; |
} |
/* ---------------------------------------------------------------------- */ |
/* sequence grabber data procedure - this is where the work is done |
/* ---------------------------------------------------------------------- */ |
/* MungGrabDataProc - the sequence grabber calls the data function whenever |
any of the grabberÕs channels write digitized data to the destination movie file. |
NOTE: We really mean any, if you have an audio and video channel then the DataProc will |
be called for either channel whenever data has been captured. Be sure to check which |
channel is being passed in. In this example we never create an audio channel so we know |
we're always dealing with video. |
This data function does two things, it first decompresses captured video |
data into an offscreen GWorld, draws some status information onto the frame then |
transfers the frame to an onscreen window. |
For more information refer to Inside Macintosh: QuickTime Components, page 5-120 |
c - the channel component that is writing the digitized data. |
p - a pointer to the digitized data. |
len - the number of bytes of digitized data. |
offset - a pointer to a field that may specify where you are to write the digitized data, |
and that is to receive a value indicating where you wrote the data. |
chRefCon - per channel reference constant specified using SGSetChannelRefCon. |
time - the starting time of the data, in the channelÕs time scale. |
writeType - the type of write operation being performed. |
seqGrabWriteAppend - Append new data. |
seqGrabWriteReserve - Do not write data. Instead, reserve space for the amount of data |
specified in the len parameter. |
seqGrabWriteFill - Write data into the location specified by offset. Used to fill the space |
previously reserved with seqGrabWriteReserve. The Sequence Grabber may |
call the DataProc several times to fill a single reserved location. |
refCon - the reference constant you specified when you assigned your data function to the sequence grabber. |
*/ |
pascal OSErr MungGrabDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon) |
{ |
#pragma unused(offset,chRefCon,writeType,refCon) |
CGrafPtr theSavedPort; |
GDHandle theSavedDevice; |
CodecFlags ignore; |
float fps = 0, |
averagefps = 0; |
char status[64]; |
Str255 theString; |
ComponentResult err = noErr; |
// reset frame and time counters after a stop/start |
if (gMungData->lastTime > time) { |
gMungData->lastTime = 0; |
gMungData->frameCount = 0; |
} |
gMungData->frameCount++; |
if (gMungData->timeScale == 0) { |
// first time here so set the time scale |
err = SGGetChannelTimeScale(c, &gMungData->timeScale); |
BailErr(err); |
} |
if (gMungData->pGWorld) { |
if (gMungData->decomSeq == 0) { |
// Set up getting grabbed data into the GWorld |
Rect sourceRect = { 0, 0 }; |
MatrixRecord scaleMatrix; |
ImageDescriptionHandle imageDesc = (ImageDescriptionHandle)NewHandle(0); |
// retrieve a channelÕs current sample description, the channel returns a sample description that is |
// appropriate to the type of data being captured |
err = SGGetChannelSampleDescription(c, (Handle)imageDesc); |
BailErr(err); |
/***** IMPORTANT NOTE ***** |
Previous versions of this sample code made an incorrect decompression |
request. Intending to draw the DV frame at quarter-size into a quarter-size |
offscreen GWorld, it made the call |
err = DecompressSequenceBegin(..., &rect, nil, ...); |
passing a quarter-size rectangle as the source rectangle. The correct |
interpretation of this request is to draw the top-left corner of the DV |
frame cropped at normal size. Unfortunately, a DV-specific bug in QuickTime |
5 caused it to misinterpret this request and scale the frame to fit. |
This bug will be fixed in QuickTime 6. If your code behaves as intended |
because of the bug, you should fix your code to pass a matrix scaling the |
frame to fit the offscreen gworld: |
RectMatrix( & scaleMatrix, &dvFrameRect, &gworldBounds ); |
err = DecompressSequenceBegin(..., nil, &scaleMatrix, ...); |
This approach will work in all versions of QuickTime. |
**************************/ |
// make a scaling matrix for the sequence |
sourceRect.right = (**imageDesc).width; |
sourceRect.bottom = (**imageDesc).height; |
RectMatrix(&scaleMatrix, &sourceRect, &gMungData->boundsRect); |
// begin the process of decompressing a sequence of frames |
// this is a set-up call and is only called once for the sequence - the ICM will interrogate different codecs |
// and construct a suitable decompression chain, as this is a time consuming process we don't want to do this |
// once per frame (eg. by using DecompressImage) |
// for more information see Ice Floe #8 http://developer.apple.com/quicktime/icefloe/dispatch008.html |
// the destination is specified as the GWorld |
err = DecompressSequenceBegin(&gMungData->decomSeq, // pointer to field to receive unique ID for sequence |
imageDesc, // handle to image description structure |
gMungData->pGWorld, // port for the DESTINATION image |
NULL, // graphics device handle, if port is set, set to NULL |
NULL, // source rectangle defining the portion of the image to decompress |
&scaleMatrix, // transformation matrix |
srcCopy, // transfer mode specifier |
(RgnHandle)NULL, // clipping region in dest. coordinate system to use as a mask |
NULL, // flags |
codecNormalQuality, // accuracy in decompression |
bestSpeedCodec); // compressor identifier or special identifiers ie. bestSpeedCodec |
BailErr(err); |
DisposeHandle((Handle)imageDesc); |
// Set up getting grabbed data into the Window |
// create the image sequence for the offscreen |
err = MakeImageSequenceForGWorld(gMungData->pGWorld, |
GetWindowPort(gMungData->pWindow), |
&gMungData->drawSize, |
&gMungData->drawSeq); |
BailErr(err); |
} |
// decompress a frame into the GWorld - can queue a frame for async decompression when passed in a completion proc |
err = DecompressSequenceFrameS(gMungData->decomSeq, // sequence ID returned by DecompressSequenceBegin |
p, // pointer to compressed image data |
len, // size of the buffer |
0, // in flags |
&ignore, // out flags |
NULL); // async completion proc |
if (err) { |
// show the error |
TextSize(10); |
TextMode(srcXor); |
MoveTo(gMungData->boundsRect.left + 10, gMungData->boundsRect.top + 80); |
sprintf(status,"DecompressSequenceFrameS gave error %ld (%lx)",err,err); |
CopyCStringToPascal(status, theString); |
DrawString(theString); |
err = noErr; |
} else { |
// write status information onto the frame |
GetGWorld(&theSavedPort, &theSavedDevice); |
SetGWorld(gMungData->pGWorld, NULL); |
TextSize(12); |
TextMode(srcCopy); |
MoveTo(gMungData->boundsRect.left +10, gMungData->boundsRect.bottom - 14); |
fps = (float)gMungData->timeScale / (float)(time - gMungData->lastTime); |
averagefps = ((float)gMungData->frameCount * (float)gMungData->timeScale) / (float)time; |
sprintf(status, "time stamp: %ld, fps:%5.1f average fps:%5.1f", time, fps, averagefps); |
CopyCStringToPascal(status, theString); |
DrawString(theString); |
SetGWorld(theSavedPort, theSavedDevice); |
// draw the frame to the destination, in this case the onscreen window |
err = DecompressSequenceFrameS(gMungData->drawSeq, // sequence ID |
GetPixBaseAddr(GetGWorldPixMap(gMungData->pGWorld)), // pointer image data |
gMungData->drawSize, // size of the buffer |
0, // in flags |
&ignore, // out flags |
NULL); // can async help us? |
} |
} |
bail: |
gMungData->lastTime = time; |
return err; |
} |
// -------------------- |
// DoUpdate |
// |
void DoUpdate(void) |
{ |
CodecFlags ignore; |
// draw the last frame captured |
DecompressSequenceFrameS(gMungData->drawSeq, |
GetPixBaseAddr(GetGWorldPixMap(gMungData->pGWorld)), |
gMungData->drawSize, |
0, |
&ignore, |
NULL); |
} |
// -------------------- |
// MakeSequenceGrabber |
// |
SeqGrabComponent MakeSequenceGrabber(WindowRef pWindow) |
{ |
SeqGrabComponent seqGrab = NULL; |
OSErr err = noErr; |
// open the default sequence grabber |
seqGrab = OpenDefaultComponent(SeqGrabComponentType, 0); |
if (seqGrab != NULL) { |
// initialize the default sequence grabber component |
err = SGInitialize(seqGrab); |
if (err == noErr) |
// set its graphics world to the specified window |
err = SGSetGWorld(seqGrab, GetWindowPort(pWindow), NULL ); |
if (err == noErr) |
// specify the destination data reference for a record operation |
// tell it we're not making a movie |
// if the flag seqGrabDontMakeMovie is used, the sequence grabber still calls |
// your data function, but does not write any data to the movie file |
// writeType will always be set to seqGrabWriteAppend |
err = SGSetDataRef(seqGrab, |
0, |
0, |
seqGrabDontMakeMovie); |
} |
if (err && (seqGrab != NULL)) { // clean up on failure |
CloseComponent(seqGrab); |
seqGrab = NULL; |
} |
return seqGrab; |
} |
// -------------------- |
// MakeSequenceGrabChannel |
// |
OSErr MakeSequenceGrabChannel(SeqGrabComponent seqGrab, SGChannel *sgchanVideo, Rect const *rect) |
{ |
long flags = 0; |
OSErr err = noErr; |
err = SGNewChannel(seqGrab, VideoMediaType, sgchanVideo); |
if (err == noErr) { |
err = SGSetChannelBounds(*sgchanVideo, rect); |
if (err == noErr) |
// set usage for new video channel to avoid playthrough |
// note we don't set seqGrabPlayDuringRecord |
err = SGSetChannelUsage(*sgchanVideo, flags | seqGrabRecord ); |
if (err != noErr) { |
// clean up on failure |
SGDisposeChannel(seqGrab, *sgchanVideo); |
*sgchanVideo = NULL; |
} |
} |
return err; |
} |
// -------------------- |
// MakeAWindow |
// |
OSErr MakeAWindow(WindowRef *outWindow) |
{ |
Rect windowRect = {0, 0, 240, 320}; |
Rect bestRect; |
OSErr err = noErr; |
// figure out the best monitor for the window |
GetBestDeviceRect(NULL, &bestRect); |
// put the window in the top left corner of that monitor |
OffsetRect(&windowRect, bestRect.left + 10, bestRect.top + 50); |
err = CreateNewWindow(kDocumentWindowClass, kWindowCloseBoxAttribute, &windowRect, outWindow); |
BailErr(err); |
SetWTitle(*outWindow, "\pMunggrab"); |
// set the port to the new window |
SetPortWindowPort(*outWindow); |
ShowWindow(*outWindow); |
bail: |
return err; |
} |
int main(void) |
{ |
WindowRef pMainWindow = NULL; |
SeqGrabComponent seqGrab; |
SGChannel sgchanVideo; |
Rect portRect; |
OSErr err = noErr; |
Initialize(); |
// create the window |
err = MakeAWindow(&pMainWindow); |
BailErr(err); |
GetPortBounds(GetWindowPort(pMainWindow), &portRect); |
// initialize our data |
err = InitializeMungData(portRect, pMainWindow); |
BailErr(err); |
// create and initialize the sequence grabber |
seqGrab = MakeSequenceGrabber(pMainWindow); |
BailErr(NULL == seqGrab); |
// create the channel |
err = MakeSequenceGrabChannel(seqGrab, &sgchanVideo, &portRect); |
BailErr(err); |
// specify a data function |
err = SGSetDataProc(seqGrab, NewSGDataUPP(MungGrabDataProc), NULL); |
BailErr(err); |
// lights...camera... |
err = SGPrepare(seqGrab, false, true); |
BailErr(err); |
// ...action |
err = SGStartRecord(seqGrab); |
BailErr(err); |
gIsGrabbing = true; |
while (!gDone) { |
EventRecord theEvent; |
WindowRef theWindow; |
GetNextEvent(everyEvent, &theEvent); |
if (IsWindowCollapsed(pMainWindow)) { |
// checking this here avoids codecNothingToBlitErr later |
SGStop(seqGrab); |
gIsGrabbing = false; |
gIsCollapsed = true; |
} |
switch (theEvent.what) { |
case nullEvent: |
// give the sequence grabber time to do it's thing |
if (gIsGrabbing) { |
err = SGIdle(seqGrab); |
if (err) { |
char errMsg[32]; |
// if there is an error, display the result in the window title |
// if it's a cDepthErr we don't pause; the sequence grabber |
// would return cDepthErr if the window was moved or depth changed on |
// QT 4.1.2, it does it less on QT 5 because Kevin made it smarter |
// all other errors cause a pause - errors set in the DataProc show up |
// here as well as others generated by the vDig - different vDigs can |
// generate different errors in different situations |
if (err == cDepthErr) { |
sprintf(errMsg, "cDepthErr ", err); |
c2pstrcpy((unsigned char *)&errMsg, errMsg); |
SetWTitle(pMainWindow, (unsigned char *)errMsg); |
SGStop(seqGrab); |
SGStartRecord(seqGrab); |
break; |
} else { |
KeyMap theKeys; |
#define ISESCKEYDOWN() ((theKeys[1] & 0x00002000) == 0x00002000) |
SGStop(seqGrab); |
sprintf(errMsg, "Stopped, esc to continue %d", err); |
c2pstrcpy((unsigned char *)&errMsg, errMsg); |
SetWTitle(pMainWindow, (unsigned char *)errMsg); |
// wait for esc |
do { |
GetKeys(theKeys); |
} while (!ISESCKEYDOWN()); |
SetWTitle(pMainWindow, "\pMunggrab"); |
SGStartRecord(seqGrab); |
} |
} |
} |
break; |
case updateEvt: |
theWindow = (WindowRef)theEvent.message; |
if (theWindow == pMainWindow) { |
if (gIsGrabbing) { |
// inform the sequence grabber of the update |
RgnHandle theUpdateRgn = NewRgn(); |
GetWindowRegion(theWindow, kWindowUpdateRgn, theUpdateRgn); |
SGUpdate(seqGrab, theUpdateRgn); |
DisposeRgn(theUpdateRgn); |
} else { |
if (!IsWindowCollapsed(pMainWindow) && gIsCollapsed) { |
// window was just un-collapsed, start grabbing again |
SGStartRecord(seqGrab); |
gIsGrabbing = true; |
gIsCollapsed = false; |
} else { |
// update the still image |
DoUpdate(); |
} |
} |
// swallow the update event |
BeginUpdate(theWindow); |
EndUpdate(theWindow); |
} |
break; |
case mouseDown: |
short nPart; |
nPart = FindWindow(theEvent.where, &theWindow); |
if (pMainWindow != theWindow) |
break; |
switch (nPart) { |
case inGoAway: |
gDone = TrackGoAway(theWindow, theEvent.where); |
break; |
case inDrag: |
ICMAlignmentProcRecord apr; |
SGGetAlignmentProc(seqGrab, &apr); |
DragAlignedWindow(theWindow, theEvent.where, &gScreenbits.bounds, NULL, &apr); |
break; |
} |
break; |
case osEvt: |
if ((theEvent.message & (suspendResumeMessage << 24)) != 0 ) { |
if ((theEvent.message & resumeFlag) != 0 ) { |
if (!gIsGrabbing) { |
// switched in, start grabbin' |
SGStartRecord(seqGrab); |
gIsGrabbing = true; |
} |
} else { |
if (gIsGrabbing) { |
// switched out, stop grabbin' |
SGStop(seqGrab); |
gIsGrabbing = false; |
} |
} |
} |
break; |
default: |
break; |
} // switch |
} |
bail: |
// clean up |
if (seqGrab) { |
SGStop(seqGrab); |
CloseComponent(seqGrab); |
} |
if (pMainWindow) |
DisposeWindow(pMainWindow); |
return 0; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14