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.
main.c
/* |
File: main.c |
Description: This example is based on the SGDataProcSamsple code which can be found at the following URL: |
http://developer.apple.com/samplecode/SGDataProcSample/SGDataProcSample.html |
While the code in SGDataProcSample used an offscreen GWorld and some QuickDraw |
calls to draw text on top of video, this sample removes the need for the extra |
GWorld by using Overlay windows and Core Graphics to draw some text and graphics |
on top of the video image being captured. |
The graphs in the upper left of the window display the following stats: |
Number of frames in the queue. |
Number of skipped frames. |
Difference between the time base time and the dataProc frame time. |
Difference between the last frame time and the current frame time. |
For each of the above stats, shorter is better. |
The status string prints out the time, a min:seconds:frames representation of time, frames per second |
and average frames per second. |
The dataProc in this case will also drop frames under certain situations. Namely if there's more than |
one frame in the queue, the capture rate drops below 15fps and we haven't skipped 15 frames in a row |
trying to catch up. There's many different strategies one could use to drop less important frames or |
always display the latest frame captured and so on. These are left as an exercise for the developer. |
Author: QuickTime Engineering, QuickTime DTS |
Copyright: © Copyright 2000 - 2005 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): 04/01/05 fixed sgchanSound failure check |
01/25/05 initial release |
*/ |
// build for carbon |
#define TARGET_API_MAC_CARBON 1 |
#include <Carbon/Carbon.h> |
#include <QuickTime/QuickTime.h> |
// defines |
#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 |
const HIViewID kBoxID = {'GBOX', 100}; |
// mung data struct |
typedef struct { |
WindowRef pWindow; // windows |
WindowRef pOverlay; |
CGContextRef context; |
CGRect textRect, barRect, offsetRect; |
CGPoint textPos; |
CGRect qfRect[25][25]; |
CGRect sfRect[25][25]; |
Rect bounds; // bounds rect |
SeqGrabComponent seqGrab; // sequence grabber |
SGChannel sgchanVideo; |
ImageSequence drawSeq; // unique identifier for our draw sequence |
TimeScale timeScale; |
TimeBase timeBase; |
UInt32 duration; |
UInt8 queuedFrameCount; |
UInt8 skipFrameCount; |
TimeValue lastTime; |
long frameCount; |
Boolean isGrabbing; |
Boolean isAccelerated; |
EventLoopTimerRef timerRef; |
OSErr err; |
} MungDataRecord, *MungDataPtr; |
// globals |
static BitMap gScreenbits; |
#pragma mark- |
OSErr SetupOverlayContext(MungDataPtr inMungData); |
void UpdateStaticText(MungDataPtr inMungData); |
OSErr MakeWindows(MungDataPtr inMungData, IBNibRef inNibRef); |
OSErr MakeSequenceGrabber(MungDataPtr pMungData); |
OSErr MakeSequenceGrabChannel(SeqGrabComponent seqGrab, SGChannel *sgchanVideo, SGChannel *sgchanSound); |
ComponentResult SetVideoChannelBounds(SGChannel videoChannel, const Rect *scaledSourceBounds, const Rect *scaledVideoBounds); |
void DisposeMungData(MungDataPtr inMungData); |
#pragma mark- |
/***************************************************** |
* |
* DisplayError(WindowRef inWindow, char inStr[], OSErr inError) |
* |
* Purpose: display an error message as the window title |
* |
* Notes: |
* |
*/ |
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- |
/***************************************************** |
* |
* MungGrabCompressCompleteBottleProc(SGChannel c, UInt8 *queuedFrameCount, SGCompressInfo *ci, TimeRecord *t, long refCon) |
* |
* Purpose: used to allow us to figure out how many frames are queued by the vDig |
* |
* Notes: the UInt8 *queuedFrameCount replaces Boolean *done. (0 (==false) still means no frames, and 1 (==true) one, |
* but if more than one are available, the number should be returned here - The value 2 previously meant more than |
* one frame, so some VDIGs may return 2 even if more than 2 are available, and some will still return 1 as they are |
* using the original definition. |
* |
*/ |
static pascal ComponentResult MungGrabCompressCompleteBottleProc(SGChannel c, UInt8 *queuedFrameCount, SGCompressInfo *ci, TimeRecord *t, long refCon) |
{ |
OSErr err; |
MungDataPtr pMungData = (MungDataPtr)refCon; |
if (NULL == pMungData) return -1; |
// call the original proc; you must do this |
err = SGGrabCompressComplete(c, queuedFrameCount, ci, t); |
// save the queued frame count so we have it |
pMungData->queuedFrameCount = *queuedFrameCount; |
return err; |
} |
/***************************************************** |
* |
* MungGrabDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon) |
* |
* Purpose: sequence grabber data procedure - this is where the work is done |
* |
* Notes: |
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. |
*/ |
static pascal OSErr MungGrabDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon) |
{ |
#pragma unused(offset,chRefCon,writeType) |
ComponentResult err = noErr; |
MungDataPtr pMungData = (MungDataPtr)refCon; |
if (NULL == pMungData) return -1; |
// reset frame and time counters after a stop/start |
if (time < pMungData->lastTime) { |
pMungData->lastTime = 0; |
pMungData->frameCount = 0; |
} |
// we only care about the video |
if (c == pMungData->sgchanVideo) { |
TimeValue timeBaseTime, timeBaseDelta, frameTimeDelta; |
pMungData->frameCount++; |
if (pMungData->timeScale == 0) { |
Fixed framesPerSecond; |
long milliSecPerFrameIgnore, bytesPerSecondIgnore; |
// first time here so get the time scale & timebase |
err = SGGetChannelTimeScale(c, &pMungData->timeScale); |
DisplayAndBail(err, "SGGetChannelTimeScale"); |
err = SGGetTimeBase(pMungData->seqGrab, &pMungData->timeBase); |
DisplayAndBail(err, "SGGetTimeBase"); |
err = VDGetDataRate(SGGetVideoDigitizerComponent(c), &milliSecPerFrameIgnore, &framesPerSecond, &bytesPerSecondIgnore); |
DisplayAndBail(err, "VDGetDataRate"); |
pMungData->duration = pMungData->timeScale / (framesPerSecond >> 16); |
} |
if (pMungData->drawSeq == 0) { |
// set up decompression sequence |
Rect sourceRect = { 0, 0 }; |
Rect scaleRect = pMungData->bounds; |
MatrixRecord scaleMatrix; |
CodecFlags cFlags = codecNormalQuality; |
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"); |
// make a scaling matrix for the sequence |
sourceRect.right = (**imageDesc).width; |
sourceRect.bottom = (**imageDesc).height; |
if (pMungData->isAccelerated) { |
// if accelerated and DV do high quality |
// 720x480 both fields |
if (kDVCNTSCCodecType == (**imageDesc).cType) { |
cFlags = codecHighQuality; |
} |
UnionRect(&sourceRect, &pMungData->bounds, &pMungData->bounds); |
UnionRect(&sourceRect, &pMungData->bounds, &scaleRect); |
OffsetRect(&scaleRect, short(pMungData->offsetRect.origin.x + 5), short(pMungData->offsetRect.origin.y + 5)); |
pMungData->bounds.right += short(pMungData->offsetRect.size.width + 10); |
pMungData->bounds.bottom += short(pMungData->offsetRect.size.height + 10); |
} else { |
MacSetRect(&scaleRect, 0, 0, 320, 240); |
OffsetRect(&scaleRect, short(pMungData->offsetRect.origin.x + 5), short(pMungData->offsetRect.origin.y + 5)); |
} |
RectMatrix(&scaleMatrix, &sourceRect, &scaleRect); |
SetupOverlayContext(pMungData); |
// 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 = DecompressSequenceBeginS(&pMungData->drawSeq, // pointer to field to receive unique ID for sequence |
imageDesc, // handle to image description structure |
p, // points to the compressed image data |
len, // size of the data buffer |
GetWindowPort(pMungData->pWindow), // port for the DESTINATION image |
NULL, // graphics device handle, if port is set, set to NULL |
NULL, // decompress the entire source image - no source extraction |
&scaleMatrix, // transformation matrix |
srcCopy, // transfer mode specifier |
(RgnHandle)NULL, // clipping region in dest. coordinate system to use as a mask |
0, // flags |
cFlags, // accuracy in decompression |
bestSpeedCodec); // compressor identifier or special identifiers ie. bestSpeedCodec |
DisplayAndBail(err, "DSeqBegin"); |
DisposeHandle((Handle)imageDesc); |
} |
// get the TimeBase time and figure out the delta between that time and this frame time |
timeBaseTime = GetTimeBaseTime(pMungData->timeBase, pMungData->timeScale, NULL); |
timeBaseDelta = timeBaseTime - time; |
frameTimeDelta = time - pMungData->lastTime; |
if (timeBaseDelta < 0) goto bail; // probably don't need this |
// if we have more than one queued frame and our capture rate drops below 10 frames, skip the frame to try and catch up |
if ((pMungData->queuedFrameCount > 1) && ((pMungData->timeScale / frameTimeDelta) < 10) && (pMungData->skipFrameCount < 15)) { |
++pMungData->skipFrameCount; |
--pMungData->frameCount; |
} else { |
CodecFlags ignore; |
// decompress a frame into the window - can queue a frame for async decompression when passed in a completion proc |
err = DecompressSequenceFrameS(pMungData->drawSeq, // 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, "DSeqFrameS"); |
pMungData->skipFrameCount = 0; |
pMungData->lastTime = time; |
} |
// status information - use CG to draw |
char status[64]; |
float fps, averagefps; |
UInt8 minutes, seconds, frames, index; |
CGPoint thePos; |
CGRect theRect; |
fps = (float)pMungData->timeScale / (float)frameTimeDelta; |
averagefps = ((float)pMungData->frameCount * (float)pMungData->timeScale) / (float)time; |
minutes = (time / pMungData->timeScale) / 60; |
seconds = (time / pMungData->timeScale) % 60; |
frames = (time % pMungData->timeScale) / frameTimeDelta; //pMungData->duration; |
sprintf(status, "t: %ld, %02d:%02d.%02d, fps:%5.1f av:%5.1f", time, minutes, seconds, frames, fps, averagefps); |
CGContextClearRect(pMungData->context, pMungData->barRect); |
CGContextClearRect(pMungData->context, pMungData->textRect); |
CGContextBeginPath(pMungData->context); |
// number of frames in the queue |
index = (pMungData->queuedFrameCount <= 25 ? pMungData->queuedFrameCount : 25); |
CGContextAddRects(pMungData->context, pMungData->qfRect[index-1], index); |
// number of skipped frames |
index = (pMungData->skipFrameCount <= 25 ? pMungData->skipFrameCount : 25); |
CGContextAddRects(pMungData->context, pMungData->sfRect[index-1], index); |
// difference between the timeBaseTime and the this frame time |
theRect = CGRectMake(1, 15, 6 * timeBaseDelta / pMungData->duration, 3); |
CGContextAddRect(pMungData->context, theRect); |
// difference between the last drawn frame and this frame time |
theRect = CGRectMake(1, 20, 6 * frameTimeDelta / pMungData->duration, 3); |
CGContextAddRect(pMungData->context, theRect); |
// draw the graph |
pMungData->barRect = CGContextGetPathBoundingBox(pMungData->context); |
CGContextClosePath(pMungData->context); |
CGContextSetRGBFillColor(pMungData->context, 1.0, 0.0, 0.0, 1.0 ); |
CGContextFillPath(pMungData->context); |
// text setup |
CGContextSetTextDrawingMode(pMungData->context, kCGTextInvisible); |
CGContextShowTextAtPoint(pMungData->context, pMungData->textPos.x, pMungData->textPos.y, status, strlen(status)); |
thePos = CGContextGetTextPosition(pMungData->context); |
pMungData->textRect.size.width = ((int)(thePos.x + .5) + 2.0) - pMungData->textRect.origin.x; |
// draw the background rect |
CGContextSetRGBFillColor(pMungData->context, 1.0, 0.0, 0.0, 0.2 ); |
CGContextFillRect(pMungData->context, pMungData->textRect); |
// draw the status string |
CGContextSetTextDrawingMode(pMungData->context, kCGTextFill); |
CGContextSetRGBFillColor(pMungData->context, 0.0, 1.0, 0.0, 1.0); |
CGContextShowTextAtPoint(pMungData->context, pMungData->textPos.x, pMungData->textPos.y, status, strlen(status)); |
// mark the context for update |
CGContextSynchronize(pMungData->context); |
} |
bail: |
return err; |
} |
#pragma mark- |
/***************************************************** |
* |
* MGWindowEventHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *inUserData) |
* |
* Purpose: idle timer to idle the sequence grabber |
* |
* Notes: timer should call this at least as much as the desired frame rate more if |
* capturing audio as well, we use 1/60th of a second |
* |
*/ |
static pascal void MGIdleTimer(EventLoopTimerRef inTimer, void *inUserData) |
{ |
#pragma unused(inTimer) |
OSErr err = noErr; |
MungDataPtr pMungData = MungDataPtr(inUserData); |
if (NULL == pMungData) return; |
if (pMungData->isGrabbing) 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); |
} |
} |
/***************************************************** |
* |
* MGWindowEventHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *inUserData) |
* |
* Purpose: window event handler |
* |
* Notes: |
* |
*/ |
static pascal OSStatus MGWindowEventHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *inUserData) |
{ |
WindowRef theWindow; |
OSErr err; |
OSStatus status = eventNotHandledErr; |
MungDataPtr pMungData = MungDataPtr(inUserData); |
if (NULL == pMungData) goto Done; |
UInt32 eventKind; |
eventKind = GetEventKind(theEvent); |
// we need the window ref or bail |
err = GetEventParameter(theEvent, kEventParamDirectObject, typeWindowRef, NULL, sizeof(theWindow), NULL, &theWindow); |
require_noerr(err, Done); |
switch (eventKind) { |
case kEventWindowHiding: |
// fall through, same action as kEventWindowCollapsed |
case kEventWindowCollapsed: |
// checking this here avoids passible codecNothingToBlitErr later |
if (pMungData->isGrabbing) { |
SGStop(pMungData->seqGrab); |
SetEventLoopTimerNextFireTime(pMungData->timerRef, kEventDurationForever); |
pMungData->isGrabbing = false; |
} |
break; |
case kEventWindowShown: |
// fall through |
case kEventWindowExpanded: |
CallNextEventHandler(nextHandler, theEvent); |
if (kEventWindowShown == eventKind) { |
ActivateWindow(theWindow, true); |
UpdateStaticText(pMungData); |
} |
// 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; // we handled the event |
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); |
require_noerr(err, Done); |
SGGetAlignmentProc(pMungData->seqGrab, &apr); |
DragAlignedWindow(theWindow, where, &gScreenbits.bounds, NULL, &apr); |
status = noErr; // we handled the event |
break; |
case kEventWindowClose: |
{ |
// we're done |
ProcessSerialNumber psn = { 0, kCurrentProcess }; |
AppleEvent quitEvent; |
AEDesc target; |
CallNextEventHandler(nextHandler, theEvent); |
HideWindow(pMungData->pOverlay); |
// send a Quit apple event to ourselves |
AECreateDesc(typeProcessSerialNumber, (Ptr)&psn, sizeof(ProcessSerialNumber), &target); |
AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, &target, kAutoGenerateReturnID, kAnyTransactionID, &quitEvent); |
AESend(&quitEvent, NULL, kAENoReply, kAENormalPriority, kNoTimeOut, NULL, NULL); |
AEDisposeDesc(&quitEvent); |
AEDisposeDesc(&target); |
status = noErr; // we handled the event |
} |
break; |
default: |
break; |
} |
Done: |
return status; |
} |
/***************************************************** |
* |
* HandleQuitAE(const AppleEvent *theAppleEvent, AppleEvent *reply, long inRefcon) |
* |
* Purpose: handle the Quit AppleEvent |
* |
* Notes: |
* |
*/ |
static pascal OSErr HandleQuitAE(const AppleEvent *theAppleEvent, AppleEvent *reply, long inRefcon) |
{ |
#pragma unused (theAppleEvent, reply) |
MungDataPtr pMungData = MungDataPtr(inRefcon); |
if (pMungData) { |
// we're done |
SGStop(pMungData->seqGrab); |
pMungData->isGrabbing = false; |
RemoveEventLoopTimer(pMungData->timerRef); |
SGRelease(pMungData->seqGrab); |
} |
QuitApplicationEventLoop(); |
return noErr; |
} |
/***************************************************** |
* |
* InstallEvenHandlers(MungDataPtr inMungData) |
* |
* Purpose: intalls event handlers and carbon timer |
* |
* Notes: |
* |
*/ |
static OSErr InstallEvenHandlers(MungDataPtr inMungData) |
{ |
const EventTypeSpec windowEventList[] = { kEventClassWindow, kEventWindowCollapsed, |
kEventClassWindow, kEventWindowExpanded, |
kEventClassWindow, kEventWindowClickDragRgn, |
kEventClassWindow, kEventWindowClose, |
kEventClassWindow, kEventWindowShown, |
kEventClassWindow, kEventWindowHiding }; |
OSStatus err; |
err = InstallEventLoopTimer(GetMainEventLoop(), kEventDurationNoWait, kTimerInterval, MGIdleTimer, inMungData, &inMungData->timerRef); |
require_noerr(err, CantInstallEventLoopTimer); |
err = InstallWindowEventHandler(inMungData->pWindow, NewEventHandlerUPP(MGWindowEventHandler), 7, windowEventList, inMungData, NULL); |
require_noerr(err, CantInstallWindowEventHandler); |
err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP(HandleQuitAE), (long)inMungData, false); |
CantInstallEventLoopTimer: |
CantInstallWindowEventHandler: |
return err; |
} |
#pragma mark- |
/***************************************************** |
* |
* SetupOverlayContext(MungDataPtr inMungData) |
* |
* Purpose: creates and sets up the CGContext for our overlay window |
* this is where the text is rendered by the DataProc |
* |
* Notes: |
* |
*/ |
OSErr SetupOverlayContext(MungDataPtr inMungData) |
{ |
Rect portRect; |
OSErr err = noErr; |
SizeWindow(inMungData->pWindow, inMungData->bounds.right, inMungData->bounds.bottom, false); |
SizeWindow(inMungData->pOverlay, inMungData->bounds.right, inMungData->bounds.bottom, false); |
GetPortBounds(GetWindowPort(inMungData->pWindow), &inMungData->bounds); |
// create a context for the overlay window and transform to QD coordinates |
err = CreateCGContextForPort(GetWindowPort(inMungData->pOverlay), &inMungData->context); |
require_noerr(err, CantCreateContext); |
GetPortBounds(GetWindowPort(inMungData->pOverlay), &portRect); |
SyncCGContextOriginWithPort(inMungData->context, GetWindowPort(inMungData->pOverlay)); |
CGContextTranslateCTM(inMungData->context, 0, (float)(portRect.bottom - portRect.top)); |
CGContextScaleCTM(inMungData->context, 1.0, -1.0); |
CGContextClearRect(inMungData->context, CGRectMake(0, |
0, |
(float)(portRect.right - portRect.left), |
(float)(portRect.bottom - portRect.top))); |
CGContextSetTextMatrix(inMungData->context, CGContextGetCTM(inMungData->context)); |
CGContextSelectFont(inMungData->context, "Helvetica", 10.0, kCGEncodingMacRoman); |
CGContextSetLineWidth(inMungData->context, 1.0); |
// size of the semi-transparent text background rect we draw |
inMungData->textRect = CGRectMake(inMungData->offsetRect.origin.x + 8, |
((float)(portRect.bottom) - inMungData->offsetRect.size.height), |
(float)portRect.right, // this is adjusted during draw |
(float)18); |
// position of text we draw |
inMungData->textPos = CGPointMake(inMungData->offsetRect.origin.x + 10, |
((float)(portRect.bottom) - inMungData->offsetRect.size.height + 13)); |
UpdateStaticText(inMungData); |
CantCreateContext: |
return err; |
} |
/***************************************************** |
* |
* UpdateStaticText(MungDataPtr inMungData) |
* |
* Purpose: update the static text when needed |
* |
* Notes: |
* |
*/ |
void UpdateStaticText(MungDataPtr inMungData) |
{ |
char *yes = "Quartz: Acclerated"; |
char *nope = "Quartz: Not Accelerated"; |
CGContextSetTextDrawingMode(inMungData->context, kCGTextFill); |
CGContextSetRGBFillColor(inMungData->context, 1.0, 0.0, 0.0, 1.0); |
if (inMungData->isAccelerated) { |
CGContextShowTextAtPoint(inMungData->context, inMungData->textPos.x, inMungData->textPos.y - 15, yes, strlen(yes)); |
} else { |
CGContextShowTextAtPoint(inMungData->context, inMungData->textPos.x, inMungData->textPos.y - 15, nope, strlen(nope)); |
} |
} |
#pragma mark- |
/***************************************************** |
* |
* MakeWindows(MungDataPtr inMungData, IBNibRef inNibRef) |
* |
* Purpose: create main and overlay windows |
* |
* Notes: |
* |
*/ |
OSErr MakeWindows(MungDataPtr inMungData, IBNibRef inNibRef) |
{ |
Rect windowRect = {0, 0, 240, 320}; |
Rect bestRect; |
HIViewRef control; |
WindowGroupRef windowGroup; |
WindowAttributes oAttributes = kWindowNoShadowAttribute | |
kWindowIgnoreClicksAttribute | kWindowNoActivatesAttribute; |
OSErr err = noErr; |
// Create a window. "MainWindow" is the name of the window object. This name is set in |
// InterfaceBuilder when the nib is created. |
err = CreateWindowFromNib(inNibRef, CFSTR("MainWindow"), &inMungData->pWindow); |
require_noerr( err, CantCreateWindow); |
GetWindowBounds(inMungData->pWindow, kWindowContentRgn, &windowRect); |
// create the overlay window |
err = CreateNewWindow(kOverlayWindowClass, oAttributes, &windowRect, &inMungData->pOverlay); |
require_noerr(err, CantCreateWindow); |
// create the window group and group the two windows together |
CreateWindowGroup(kWindowGroupAttrMoveTogether | |
kWindowGroupAttrLayerTogether | |
kWindowGroupAttrSharedActivation | |
kWindowGroupAttrHideOnCollapse, &windowGroup); |
SetWindowGroupParent(windowGroup, GetWindowGroup(inMungData->pWindow)); |
SetWindowGroup(inMungData->pWindow, windowGroup); |
SetWindowGroup(inMungData->pOverlay, windowGroup); |
// set the port to the new window |
SetPortWindowPort(inMungData->pWindow); |
GetPortBounds(GetWindowPort(inMungData->pWindow), &inMungData->bounds); |
AlignWindow(inMungData->pWindow, true, NULL, NULL); |
// get the frame size of the group box, we draw the video at this offset |
HIViewFindByID(HIViewGetRoot(inMungData->pWindow), kBoxID, &control); |
HIViewGetFrame(control, &inMungData->offsetRect); |
inMungData->offsetRect.size.width = inMungData->bounds.right - inMungData->offsetRect.size.width; |
inMungData->offsetRect.size.height = inMungData->bounds.bottom - inMungData->offsetRect.size.height; |
ShowWindow(inMungData->pWindow); |
ShowWindow(inMungData->pOverlay); |
inMungData->isAccelerated = CGDisplayUsesOpenGLAcceleration(kCGDirectMainDisplay); |
CantCreateWindow: |
return err; |
} |
/***************************************************** |
* |
* MakeSequenceGrabber(MungDataPtr inMungData) |
* |
* Purpose: open and configure the sequence grabber |
* |
* Notes: |
* |
*/ |
OSErr MakeSequenceGrabber(MungDataPtr inMungData) |
{ |
OSErr err = couldntGetRequiredComponent; |
// open the default sequence grabber |
inMungData->seqGrab = OpenDefaultComponent(SeqGrabComponentType, 0); |
require(inMungData->seqGrab != NULL, CantGetSG); |
// initialize the default sequence grabber component |
err = SGInitialize(inMungData->seqGrab); |
require_noerr(err, Bail); |
// set its graphics world to the specified window |
err = SGSetGWorld(inMungData->seqGrab, GetWindowPort(inMungData->pWindow), NULL); |
require_noerr(err, Bail); |
// 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(inMungData->seqGrab, |
0, |
0, |
seqGrabDontMakeMovie | seqGrabDataProcIsInterruptSafe); |
Bail: |
if (err && (inMungData->seqGrab != NULL)) { // clean up on failure |
CloseComponent(inMungData->seqGrab); |
inMungData->seqGrab = NULL; |
} |
CantGetSG: |
return err; |
} |
/***************************************************** |
* |
* MakeSequenceGrabChannel(SeqGrabComponent seqGrab, SGChannel *sgchanVideo, SGChannel *sgchanSound) |
* |
* Purpose: create the channels |
* |
* Notes: |
* |
*/ |
OSErr MakeSequenceGrabChannel(SeqGrabComponent seqGrab, SGChannel *sgchanVideo, SGChannel *sgchanSound) |
{ |
Rect srcBounds; |
OSErr err = noErr; |
err = SGNewChannel(seqGrab, VideoMediaType, sgchanVideo); |
if (err == noErr) { |
err = SGNewChannel(seqGrab, SoundMediaType, sgchanSound); |
if (err) { |
// don't care if we couldn't get a sound channel |
*sgchanSound = NULL; |
err = noErr; |
} |
} |
if (err == noErr) { |
// get the active rectangle |
err = SGGetSrcVideoBounds(*sgchanVideo, &srcBounds); |
if (err == noErr) { |
// we always want all the source |
err = SetVideoChannelBounds(*sgchanVideo, &srcBounds, &srcBounds); |
if (err == noErr) { |
// set usage for new video channel to avoid playthrough |
// note we don't set seqGrabPlayDuringRecord |
err = SGSetChannelUsage(*sgchanVideo, seqGrabRecord | |
seqGrabLowLatencyCapture | |
seqGrabAlwaysUseTimeBase); |
if (err == noErr && NULL != *sgchanSound) { |
err = SGSetChannelUsage(*sgchanSound, seqGrabRecord | |
//seqGrabPlayDuringRecord | |
seqGrabLowLatencyCapture | |
seqGrabAlwaysUseTimeBase); |
} |
} |
} |
if (err != noErr) { |
// clean up on failure |
SGDisposeChannel(seqGrab, *sgchanVideo); |
if (NULL != *sgchanSound) { |
SGDisposeChannel(seqGrab, *sgchanSound); |
} |
} |
} |
return err; |
} |
/***************************************************** |
* |
* SetVideoChannelBounds(SGChannel videoChannel, const Rect *scaledSourceBounds, const Rect *scaledVideoBounds) |
* |
* Purpose: sets the desired video rect and video channel bounds |
* |
* Notes: see Q&A 1250 |
* |
*/ |
ComponentResult SetVideoChannelBounds(SGChannel videoChannel, const Rect *scaledSourceBounds, const Rect *scaledVideoBounds) |
{ |
Rect sourceBounds; |
Rect videoBounds; |
Rect channelBounds; |
MatrixRecord scaledSourceBoundsToSourceBounds; |
ComponentResult err; |
// calculate the matrix to transform the |
// scaledSourceBounds to the source bounds |
SGGetSrcVideoBounds(videoChannel, &sourceBounds); |
RectMatrix(&scaledSourceBoundsToSourceBounds, |
scaledSourceBounds, &sourceBounds); |
// apply the same transform to the |
// scaledVideoBounds to get the video bounds |
videoBounds = *scaledVideoBounds; |
TransformRect(&scaledSourceBoundsToSourceBounds, &videoBounds, 0); |
err = SGSetVideoRect(videoChannel, &videoBounds); |
if (err) { |
// some video digitizers may only be able to capture full frame |
// and will return qtParamErr or possibly digiUnimpErr |
// if they can't handle working with less than full frame |
SGSetVideoRect(videoChannel, &sourceBounds); |
} |
// the channel bounds is scaledVideoBounds offset to (0, 0) |
channelBounds = *scaledVideoBounds; |
OffsetRect(&channelBounds, -channelBounds.left, -channelBounds.top); |
// Note: SGSetChannelBounds merely allows the client to |
// specify it's preferred bounds. The actual bounds returned |
// by the vDig in the image description may be different |
err = SGSetChannelBounds(videoChannel, &channelBounds); |
return err; |
} |
/***************************************************** |
* |
* DisposeMungData(MungDataPtr inMungData) |
* |
* Purpose: end the decompression sequence, close the sequence grabber and |
* dispose of our data pointer |
* |
* Notes: |
* |
*/ |
void DisposeMungData(MungDataPtr inMungData) |
{ |
// clean up the bits |
if(inMungData) { |
if (inMungData->drawSeq) |
CDSequenceEnd(inMungData->drawSeq); |
if (inMungData->seqGrab) |
CloseComponent(inMungData->seqGrab); |
DisposePtr((Ptr)inMungData); |
} |
} |
#pragma mark- |
/***************************************************** |
* |
* main(argc, argv) |
* |
* Purpose: main program entry point |
* |
* Notes: Penguins make up the scientific order Sphenisciformes and the family Spheniscidae. |
* |
*/ |
int main(int argc, char* argv[]) |
{ |
IBNibRef nibRef; |
MungDataPtr pMungData = NULL; |
SGChannel sgchanSound = NULL; |
VideoBottles vb = { 0 }; |
OSStatus err; |
InitCursor(); |
EnterMovies(); |
GetQDGlobalsScreenBits(&gScreenbits); |
// Create a Nib reference passing the name of the nib file (without the .nib extension) |
// CreateNibReference only searches into the application bundle. |
err = CreateNibReference(CFSTR("main"), &nibRef); |
require_noerr(err, CantGetNibRef); |
// Once the nib reference is created, set the menu bar. "MainMenu" is the name of the menu bar |
// object. This name is set in InterfaceBuilder when the nib is created. |
err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar")); |
require_noerr(err, CantSetMenuBar); |
// initialize the data that's going to be passed around |
pMungData = (MungDataPtr)NewPtrClear(sizeof(MungDataRecord)); |
err = MemError(); |
require_noerr(err, CantCreateMungData); |
// create 25 rectangles which will represent our queued frames and skipped frames display |
for (UInt8 i=0; i < 25; ++i) { |
for (UInt8 x=0; x <= i; ++x) { |
pMungData->qfRect[i][x] = CGRectMake(8*x+1, 5, 6, 3); |
pMungData->sfRect[i][x] = CGRectMake(8*x+1, 10, 6, 3); |
} |
} |
// create the main and overlay windows and group them together |
err = MakeWindows(pMungData, nibRef); |
require_noerr(err, CantCreateWindow); |
// We don't need the nib reference anymore. |
DisposeNibReference(nibRef); |
// create and initialize the sequence grabber |
err = MakeSequenceGrabber(pMungData); |
require_noerr(err, CantCreateSequenceGrabber); |
// create the channels |
err = MakeSequenceGrabChannel(pMungData->seqGrab, &pMungData->sgchanVideo, &sgchanSound); |
require_noerr(err, CantCreateVideoChannel); |
// specify a sequence grabber data function |
err = SGSetDataProc(pMungData->seqGrab, NewSGDataUPP(MungGrabDataProc), (long)pMungData); |
require_noerr(err, CantSetDataProc); |
SGSetChannelRefCon(pMungData->sgchanVideo, (long)pMungData); |
// set up the video bottlenecks so we can get our queued frame count |
err = SGGetVideoBottlenecks(pMungData->sgchanVideo, &vb); |
require_noerr(err, CantGetBottlenecks); |
vb.procCount = 9; // there are 9 bottleneck procs; this must be filled in |
vb.grabCompressCompleteProc = NewSGGrabCompressCompleteBottleUPP(MungGrabCompressCompleteBottleProc); |
err = SGSetVideoBottlenecks(pMungData->sgchanVideo, &vb); |
require_noerr(err, CantSetBottlenecks); |
if (pMungData->isAccelerated) { |
SGSetFrameRate(pMungData->sgchanVideo, FixRatio(30, 1)); |
} else { |
SGSetFrameRate(pMungData->sgchanVideo, FixRatio(15, 1)); |
} |
// install carbon event handlers |
err = InstallEvenHandlers(pMungData); |
require_noerr(err, CantInstallEventHandlers); |
// lights...camera... |
err = SGPrepare(pMungData->seqGrab, false, true); |
require_noerr(err, PrepareFailed); |
// make sure the timebase used by the video channel is being driven by |
// the sound clock if there is a sound channel, this has to be done |
// after calling SGPrepare - see Q&A 1314 |
if (NULL != sgchanSound) { |
TimeBase soundTimeBase = NULL, sgTimeBase = NULL; |
err = SGGetTimeBase(pMungData->seqGrab, &sgTimeBase); |
if(noErr == err) |
err = SGGetChannelTimeBase(sgchanSound, &soundTimeBase); |
if (noErr == err && NULL != soundTimeBase) |
SetTimeBaseMasterClock(sgTimeBase, (Component)GetTimeBaseMasterClock(soundTimeBase), NULL); |
} |
// ...action |
err = SGStartRecord(pMungData->seqGrab); |
require_noerr(err, StartRecordFailed); |
pMungData->isGrabbing = true; |
// run the application |
RunApplicationEventLoop(); |
CantCreateMungData: |
CantCreateSequenceGrabber: |
CantCreateVideoChannel: |
CantSetDataProc: |
CantGetBottlenecks: |
CantSetBottlenecks: |
CantInstallEventHandlers: |
PrepareFailed: |
StartRecordFailed: |
CantCreateWindow: |
CantSetMenuBar: |
CantGetNibRef: |
// clean up |
if (pMungData->context) |
CGContextRelease(pMungData->context); |
if (pMungData->pOverlay) |
DisposeWindow(pMungData->pOverlay); |
if (pMungData->pWindow) |
DisposeWindow(pMungData->pWindow); |
DisposeMungData(pMungData); |
return err; |
} |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-08-12