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.
SonOfMungGrab.c
/* |
File: SonOfMunggrab.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. SonOfMunggrab 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. |
SonOfMunggrab is the offspring of Munggrab written by the illustrious |
Kevin Marks. While the techniques presented in the original Munggrab remain |
the same, this sample throws Carbon Events into the fray for better performance |
on Mac OS X. |
Author: km, era |
Copyright: © Copyright 2000 - 2001 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 and born as SonOfMunggrab |
<1> 1/13/00 initial release |
*/ |
// NOTE: This is a CARBON sample and uses the Carbon Event Model. |
// For best results use CarbonLib 1.3.1, Universal Interfaces 3.4 |
// QuickTime 5, a DV camera of some sort and a large polo-mallet. |
// 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;} |
#define DisplayAndBail(x, y) {pMungData->err = x; if(x != noErr) { DisplayError(pMungData->pWindow, y, x); goto bail;}} |
// constants |
const EventTime kTimerInterval = kEventDurationSecond / 60; // idle timer interval |
// mung data struct |
typedef struct { |
WindowRef pWindow; // window |
Rect bounds; // 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; |
Boolean isGrabbing; |
EventLoopTimerRef timerRef; |
OSErr err; |
} MungDataRecord, *MungDataPtr; |
// globals |
static BitMap gScreenbits; |
// -------------------- |
// Initialize for Carbon & QuickTime |
// |
void InitializeApp(void); |
void InitializeApp(void) |
{ |
long response = 0; |
InitCursor(); |
// draw a menu bar on X so Quit will be seen |
OSErr err = Gestalt(gestaltMenuMgrAttr, &response); |
if ((err == noErr) && (response & gestaltMenuMgrAquaLayoutMask)) |
{ |
Handle menuBar = GetNewMBar(128); |
SetMenuBar(menuBar); |
DrawMenuBar(); |
} |
EnterMovies(); |
GetQDGlobalsScreenBits(&gScreenbits); |
} |
// -------------------- |
// InitializeMungData |
// |
MungDataPtr InitializeMungData(WindowRef inWindow, Rect inBounds, SeqGrabComponent inSeqGrab); |
MungDataPtr InitializeMungData(WindowRef inWindow, Rect inBounds, SeqGrabComponent inSeqGrab) |
{ |
MungDataPtr pMungData = NULL; |
CGrafPtr theOldPort; |
GDHandle theOldDevice; |
OSErr err = noErr; |
// allocate memory for the data |
pMungData = (MungDataPtr)NewPtrClear(sizeof(MungDataRecord)); |
if (MemError() || NULL == pMungData ) return NULL; |
// create a GWorld |
err = QTNewGWorld(&(pMungData->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(pMungData->pGWorld))) |
goto bail; |
GetGWorld(&theOldPort, &theOldDevice); |
SetGWorld(pMungData->pGWorld, NULL); |
BackColor(blackColor); |
ForeColor(whiteColor); |
EraseRect(&inBounds); |
SetGWorld(theOldPort, theOldDevice); |
pMungData->pWindow = inWindow; |
pMungData->bounds = inBounds; |
pMungData->seqGrab = inSeqGrab; |
pMungData->isGrabbing = false; |
return pMungData; |
bail: |
// something's bust, clean up and get out |
if (pMungData) { |
if (pMungData->pGWorld) DisposeGWorld(pMungData->pGWorld); |
DisposePtr((Ptr)pMungData); |
} |
return NULL; |
} |
// -------------------- |
// MakeAWindow |
// |
OSErr MakeAWindow(WindowRef *outWindow); |
OSErr MakeAWindow(WindowRef *outWindow) |
{ |
Rect windowRect = {0, 0, 240, 320}; |
Rect bestRect; |
WindowAttributes wAttributes = kWindowCloseBoxAttribute | |
kWindowCollapseBoxAttribute | |
kWindowStandardHandlerAttribute | |
kWindowInWindowMenuAttribute; |
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); |
BailErr(CreateNewWindow(kDocumentWindowClass, wAttributes, &windowRect, outWindow)); |
SetWTitle(*outWindow, "\pSonOfMunggrab"); |
ShowWindow(*outWindow); |
// set the port to the new window |
SetPortWindowPort(*outWindow); |
bail: |
return err; |
} |
// -------------------- |
// DisposeMungData |
// |
void DisposeMungData(MungDataPtr pMungData); |
void DisposeMungData(MungDataPtr pMungData) |
{ |
// clean up the bits |
if(pMungData) { |
if (pMungData->decomSeq) |
CDSequenceEnd(pMungData->decomSeq); |
if (pMungData->drawSeq) |
CDSequenceEnd(pMungData->drawSeq); |
if (pMungData->seqGrab) |
CloseComponent(pMungData->seqGrab); |
if (pMungData->pGWorld) { |
DisposeGWorld(pMungData->pGWorld); |
pMungData->pGWorld = NULL; |
} |
DisposePtr((Ptr)pMungData); |
pMungData = NULL; |
} |
} |
static void DisplayError(WindowRef inWindow, char inStr[], OSErr inError) |
{ |
// set the window title to display the error |
char errMsg[64]; |
sprintf(errMsg, "%s: %d", inStr, inError); |
CopyCStringToPascal(errMsg, (unsigned char *)&errMsg); |
SetWTitle(inWindow, (unsigned char *)errMsg); |
} |
#pragma mark- |
// -------------------- |
// MakeSequenceGrabber |
// |
SeqGrabComponent MakeSequenceGrabber(WindowRef pWindow); |
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); |
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; |
} |
#pragma mark- |
// -------------------- |
// MakeImageSequenceForGWorld |
// |
OSErr MakeImageSequenceForGWorld(GWorldPtr pGWorld, GWorldPtr pDest, long *imageSize, ImageSequence *seq); |
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); |
pascal OSErr MungGrabDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon) |
{ |
#pragma unused(offset,chRefCon,writeType) |
CGrafPtr theSavedPort; |
GDHandle theSavedDevice; |
CodecFlags ignore; |
float fps = 0, |
averagefps = 0; |
ComponentResult err = noErr; |
MungDataPtr pMungData = (MungDataPtr)refCon; |
if (NULL == pMungData) return -1; |
// reset frame and time counters after a stop/start |
if (pMungData->lastTime > time) { |
pMungData->lastTime = 0; |
pMungData->frameCount = 0; |
} |
pMungData->frameCount++; |
if (pMungData->timeScale == 0) { |
// first time here so set the time scale |
err = SGGetChannelTimeScale(c, &pMungData->timeScale); |
DisplayAndBail(err, "SGGetChannelTimeScale"); |
} |
if (pMungData->pGWorld) { |
if (pMungData->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); |
DisplayAndBail(err, "SGGetChannelSampleDescription"); |
/***** 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, &pMungData->bounds); |
// 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(&pMungData->decomSeq, // pointer to field to receive unique ID for sequence |
imageDesc, // handle to image description structure |
pMungData->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 |
DisplayAndBail(err, "GWorldDecompressSequenceBegin"); |
DisposeHandle((Handle)imageDesc); |
// Set up getting grabbed data into the Window |
// create the image sequence for the offscreen |
err = MakeImageSequenceForGWorld(pMungData->pGWorld, |
GetWindowPort(pMungData->pWindow), |
&pMungData->drawSize, |
&pMungData->drawSeq); |
DisplayAndBail(err, "MakeImageSequenceForGWorld"); |
} |
// decompress a frame into the GWorld - can queue a frame for async decompression when passed in a completion proc |
err = DecompressSequenceFrameS(pMungData->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 |
DisplayAndBail(err, "GWorldDecompressSequenceFrameS"); |
// Write status information onto the frame |
char status[64]; |
Str255 theString; |
GetGWorld(&theSavedPort, &theSavedDevice); |
SetGWorld(pMungData->pGWorld, NULL); |
TextSize(12); |
TextMode(srcCopy); |
MoveTo(pMungData->bounds.left +10, pMungData->bounds.bottom - 14); |
fps = (float)pMungData->timeScale / (float)(time - pMungData->lastTime); |
averagefps = ((float)pMungData->frameCount * (float)pMungData->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(pMungData->drawSeq, // sequence ID |
GetPixBaseAddr(GetGWorldPixMap(pMungData->pGWorld)), // pointer image data |
pMungData->drawSize, // size of the buffer |
0, // in flags |
&ignore, // out flags |
NULL); // can async help us? |
DisplayAndBail(err, "WindowDecompressSequenceFrameS"); |
} |
bail: |
pMungData->lastTime = time; |
return err; |
} |
// -------------------- |
// DoUpdate |
// |
void DoUpdate(MungDataPtr pMungData); |
void DoUpdate(MungDataPtr pMungData) |
{ |
CodecFlags ignore; |
// draw the last frame |
pMungData->err = DecompressSequenceFrameS(pMungData->drawSeq, |
GetPixBaseAddr(GetGWorldPixMap(pMungData->pGWorld)), |
pMungData->drawSize, |
0, |
&ignore, |
NULL); |
if (pMungData->err) |
DisplayError(pMungData->pWindow, "DoUpdate", pMungData->err); |
} |
#pragma mark- |
// -------------------- |
// MGIdleTimer |
// |
// Munggrab idle timer to idle the sequence grabber, call this at least |
// as much as the desired frame rate - more is better |
static pascal void MGIdleTimer(EventLoopTimerRef inTimer, void *inUserData) |
{ |
#pragma unused(inTimer) |
MungDataPtr pMungData = MungDataPtr(inUserData); |
if (NULL == pMungData) return; |
OSErr err = SGIdle(pMungData->seqGrab); |
if (err && err != pMungData->err) { |
// some error specific to SGIdle occurred - any errors returned from the |
// data proc will also show up here and we don't want to write over them |
// in QT 4 you would always encounter a cDepthErr error after a user drags |
// the window, this failure condition has been greatly relaxed in QT 5 |
// it may still occur but should only apply to vDigs that really control |
// the screen |
// you don't always know where these errors originate from, some may come |
// from the VDig... |
DisplayError(pMungData->pWindow, "SGIdle", err); |
// ...to fix this we simply call SGStop and SGStartRecord again |
// calling stop allows the SG to release and re-prepare for grabbing |
// hopefully fixing any problems, this is obviously a very relaxed |
// approach |
SGStop(pMungData->seqGrab); |
SGStartRecord(pMungData->seqGrab); |
} |
} |
// -------------------- |
// MGAppEventHandler |
// |
// Munggrab application event handler |
static pascal OSStatus MGAppEventHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *inUserData) |
{ |
#pragma unused(nextHandler) |
OSStatus status = eventNotHandledErr; |
MungDataPtr pMungData = MungDataPtr(inUserData); |
if (NULL == pMungData) goto done; |
UInt32 eventKind; |
eventKind = GetEventKind(theEvent); |
switch (eventKind) { |
case kEventAppActivated: |
if (!pMungData->isGrabbing) { |
// switched in, if the window isn't collapsed start grabbing |
// if it is collapsed let the kEventWindowExpanded event |
// start things up again |
if (!IsWindowCollapsed(pMungData->pWindow)) { |
SGStartRecord(pMungData->seqGrab); |
SetEventLoopTimerNextFireTime(pMungData->timerRef, kEventDurationNoWait); |
pMungData->isGrabbing = true; |
} |
} |
status = noErr; |
break; |
case kEventAppDeactivated: |
if (pMungData->isGrabbing) { |
// switched out, stop grabbin' |
SGStop(pMungData->seqGrab); |
SetEventLoopTimerNextFireTime(pMungData->timerRef, kEventDurationForever); |
pMungData->isGrabbing = false; |
} |
status = noErr; |
break; |
default: |
break; |
} |
done: |
return status; |
} |
// -------------------- |
// MGWindowEventHandler |
// |
// Munggrab window event handler |
static pascal OSStatus MGWindowEventHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *inUserData) |
{ |
#pragma unused(nextHandler) |
WindowRef theWindow; |
OSStatus status = eventNotHandledErr; |
MungDataPtr pMungData = MungDataPtr(inUserData); |
if (NULL == pMungData) goto done; |
UInt32 eventKind; |
eventKind = GetEventKind(theEvent); |
// we need the window ref or bail |
OSErr err; |
err = GetEventParameter(theEvent, kEventParamDirectObject, typeWindowRef, NULL, sizeof(theWindow), NULL, &theWindow); |
if (err) goto done; |
switch (eventKind) { |
case kEventWindowUpdate: |
if (pMungData->isGrabbing) { |
// inform the sequence grabber of the update |
RgnHandle theUpdateRgn = NewRgn(); |
GetWindowRegion(theWindow, kWindowUpdateRgn, theUpdateRgn); |
SGUpdate(pMungData->seqGrab, theUpdateRgn); |
DisposeRgn(theUpdateRgn); |
} else { |
DoUpdate(pMungData); |
} |
// swallow the update event |
BeginUpdate(theWindow); |
EndUpdate(theWindow); |
status = noErr; |
break; |
case kEventWindowHiding: |
// fall through, same action as kEventWindowCollapsed |
case kEventWindowCollapsed: |
// checking this here avoids codecNothingToBlitErr later |
if (pMungData->isGrabbing) { |
SGStop(pMungData->seqGrab); |
SetEventLoopTimerNextFireTime(pMungData->timerRef, kEventDurationForever); |
pMungData->isGrabbing = false; |
} |
status = noErr; |
break; |
case kEventWindowShown: |
// on 9.1 with CarbonLib 1.3.1 we DON'T get this event, when the window is shown |
// an update event is generated and we update; on X we DO receive this event but |
// receive NO update event, so we update ourselves |
DoUpdate(pMungData); |
status = noErr; |
break; |
case kEventWindowExpanded: |
// we stopped grabbing in the collapsed event so start grabbing on expanded |
if (!pMungData->isGrabbing) { |
SGStartRecord(pMungData->seqGrab); |
SetEventLoopTimerNextFireTime(pMungData->timerRef, kEventDurationNoWait); |
pMungData->isGrabbing = true; |
} |
status = noErr; |
break; |
case kEventWindowClickDragRgn: |
Point where; |
ICMAlignmentProcRecord apr; |
// we need the 'where' param from the Event for DragAlignedWindow |
err = GetEventParameter(theEvent, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(where), NULL, &where); |
if (err) goto done; |
SGGetAlignmentProc(pMungData->seqGrab, &apr); |
DragAlignedWindow(theWindow, where, &gScreenbits.bounds, NULL, &apr); |
status = noErr; |
break; |
case kEventWindowClose: |
// we're done |
RemoveEventLoopTimer(pMungData->timerRef); |
QuitApplicationEventLoop(); |
status = noErr; |
break; |
default: |
break; |
} |
done: |
return status; |
} |
// -------------------- |
// HandleQuit AE |
// |
pascal OSErr HandleQuitAE(const AppleEvent *theAppleEvent, AppleEvent *reply, long inRefcon); |
pascal OSErr HandleQuitAE(const AppleEvent *theAppleEvent, AppleEvent *reply, long inRefcon) |
{ |
#pragma unused (theAppleEvent, reply) |
MungDataPtr pMungData = MungDataPtr(inRefcon); |
if (pMungData) |
RemoveEventLoopTimer(pMungData->timerRef); |
QuitApplicationEventLoop(); |
return noErr; |
} |
// -------------------- |
// InstallEvenHandlers |
// |
// install carbon event handlers and timer |
// you can find more information about carbon events here: |
// http://developer.apple.com/techpubs/macosx/Carbon/carbon.html |
// http://developer.apple.com/sdk/index.html |
static OSErr InstallEvenHandlers(MungDataPtr inMungData) |
{ |
OSStatus err = eventInternalErr; |
const EventTypeSpec appEventList[] = { kEventClassApplication, kEventAppActivated, |
kEventClassApplication, kEventAppDeactivated }; |
const EventTypeSpec windowEventList[] = { kEventClassWindow, kEventWindowUpdate, |
kEventClassWindow, kEventWindowCollapsed, |
kEventClassWindow, kEventWindowExpanded, |
kEventClassWindow, kEventWindowClickDragRgn, |
kEventClassWindow, kEventWindowClose, |
kEventClassWindow, kEventWindowShown, |
kEventClassWindow, kEventWindowHiding }; |
err = InstallEventLoopTimer(GetMainEventLoop(), kEventDurationNoWait, kTimerInterval, NewEventLoopTimerUPP(MGIdleTimer), inMungData, &inMungData->timerRef); |
BailErr(err); |
err = InstallApplicationEventHandler(NewEventHandlerUPP(MGAppEventHandler), 2, appEventList, inMungData, NULL); |
BailErr(err); |
err = InstallWindowEventHandler(inMungData->pWindow, NewEventHandlerUPP(MGWindowEventHandler), 7, windowEventList, inMungData, NULL); |
BailErr(err); |
err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP(HandleQuitAE), (long)inMungData, false); |
bail: |
return err; |
} |
#pragma mark- |
// -------------------- |
int main(void) |
{ |
WindowRef pMainWindow = NULL; |
MungDataPtr pMungData = NULL; |
SeqGrabComponent seqGrab = 0; |
SGChannel sgchanVideo = 0; |
Rect portRect; |
OSErr err = noErr; |
InitializeApp(); |
// create the window |
err = MakeAWindow(&pMainWindow); |
BailErr(err); |
GetPortBounds(GetWindowPort(pMainWindow), &portRect); |
// create and initialize the sequence grabber |
seqGrab = MakeSequenceGrabber(pMainWindow); |
BailErr(NULL == seqGrab); |
// create the channel |
err = MakeSequenceGrabChannel(seqGrab, &sgchanVideo, &portRect); |
BailErr(err); |
// initialize our data that's going to be passed around |
pMungData = InitializeMungData(pMainWindow, portRect, seqGrab); |
BailErr(NULL == pMungData); |
// specify a sequence grabber data function |
err = SGSetDataProc(seqGrab, NewSGDataUPP(MungGrabDataProc), (long)pMungData); |
BailErr(err); |
// install carbon event handlers |
err = InstallEvenHandlers(pMungData); |
BailErr(err); |
// lights...camera... |
err = SGPrepare(seqGrab, false, true); |
BailErr(err); |
// ...action |
err = SGStartRecord(seqGrab); |
BailErr(err); |
pMungData->isGrabbing = true; |
// run the application |
RunApplicationEventLoop(); |
bail: |
// clean up |
if (pMainWindow) |
DisposeWindow(pMainWindow); |
DisposeMungData(pMungData); |
return 0; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14