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.
Minimung.c
/* |
File: Minimung.c |
Description: Minimung shows how to run the Sequence Grabber in record mode and use |
a DataProc to get at the captured data. This technique provides optimal |
performance, far better than using preview mode with SG 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 - 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> 4/04/00 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 <QuickTimeComponents.h> |
#include <TextUtils.h> |
#include <stdio.h> |
#endif |
#define BailErr(x) {err = x; if(err != noErr) goto bail;} |
// data |
typedef struct { |
GWorldPtr pGWorld; |
Rect boundsRect; |
WindowRef pWindow; |
ImageSequence decomSeq; // unique identifier for our decompression sequence |
ImageSequence drawSeq; // unique identifier for our draw sequence |
} tMungDataRecord, *MungDataPtr; |
// global data pointer |
static MungDataPtr gpMungData = NULL; |
OSErr InitializeMungData(Rect inBounds,const WindowRef inWindow); |
OSErr InitializeMungData(Rect inBounds,const WindowRef inWindow) |
{ |
OSErr err = noErr; |
// allocate memory for the data |
gpMungData = (MungDataPtr)NewPtrClear(sizeof(tMungDataRecord)); |
if (gpMungData ==NULL) {err = MemError(); goto bail;} |
// create the GWorld |
err = QTNewGWorld(&(gpMungData->pGWorld), // returned GWorld |
k32ARGBPixelFormat, // pixel format |
&inBounds, // bounding rectangle |
0, // color table |
NULL, // graphic device handle |
0); // flags |
BailErr(err); |
// lock the pixmap and make sure it's locked because |
// we can't decompress into an unlocked PixMap |
if(!LockPixels(GetPortPixMap(gpMungData->pGWorld))) |
BailErr(-1); |
gpMungData->boundsRect = inBounds; |
gpMungData->pWindow = inWindow; |
bail: |
return err; |
} |
void DisposeMungData(void); |
void DisposeMungData(void) |
{ |
// end the decompression sequences |
// clean up the bits |
if (gpMungData) { |
if (gpMungData->decomSeq) CDSequenceEnd(gpMungData->decomSeq); |
if (gpMungData->drawSeq) CDSequenceEnd(gpMungData->drawSeq); |
if (gpMungData->pGWorld) { |
DisposeGWorld(gpMungData->pGWorld); |
gpMungData->pGWorld = NULL; |
} |
DisposePtr((Ptr)gpMungData); |
gpMungData = NULL; |
} |
} |
/* ---------------------------------------------------------------------- */ |
/* sequence grabber data procedure - this is where the work is done |
/* ---------------------------------------------------------------------- */ |
/* mySGDataProc - 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 decompresses captured video data into an offscreen GWorld, |
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 mySGDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon); |
pascal OSErr mySGDataProc(SGChannel c, Ptr p, long len, long *offset, long chRefCon, TimeValue time, short writeType, long refCon) |
{ |
#pragma unused(offset,chRefCon,time,writeType) |
CodecFlags ignore; |
MungDataPtr theMungData = (MungDataPtr)refCon; |
ComponentResult err = noErr; |
if (!theMungData) goto bail; |
if(theMungData->pGWorld) { |
if (theMungData->decomSeq == 0) { |
Rect sourceRect = { 0, 0 }; |
MatrixRecord scaleMatrix; |
ImageDescriptionHandle imageDesc = (ImageDescriptionHandle)NewHandle(0); |
/* Set up getting grabbed data into the GWorld */ |
// 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, &theMungData->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(&theMungData->decomSeq, // pointer to field to receive unique ID for sequence |
imageDesc, // handle to image description structure |
theMungData->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 |
NULL, // clipping region in dest. coordinate system to use as a mask |
0, // flags |
codecNormalQuality, // accuracy in decompression |
bestSpeedCodec); // compressor identifier or special identifiers ie. bestSpeedCodec |
BailErr(err); |
DisposeHandle((Handle)imageDesc); |
imageDesc = NULL; |
/* Set up getting grabbed data into the Window */ |
// 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(GetPortPixMap(theMungData->pGWorld), &imageDesc); |
BailErr(err); |
// begin the process of decompressing a sequence of frames - see above notes on this call |
// destination is specified as the Window |
err = DecompressSequenceBegin(&theMungData->drawSeq, |
imageDesc, |
GetWindowPort(theMungData->pWindow), |
NULL, |
&theMungData->boundsRect, |
NULL, |
srcCopy, |
NULL, |
0, |
codecNormalQuality, |
bestSpeedCodec); |
BailErr(err); |
if (imageDesc) |
DisposeHandle((Handle)imageDesc); |
} |
// decompress a frame into the GWorld - can queue a frame for async decompression when passed in a completion proc |
// once the image is in the GWorld it can be manipulated at will |
err = DecompressSequenceFrameS(theMungData->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 |
BailErr(err); |
// ****** IMAGE IS NOW IN THE GWORLD ****** // |
// decompress one of a sequence of frames |
// this draws the image back to the window from the GWorld and could be used as a "preview" |
err = DecompressSequenceFrame(theMungData->drawSeq, // sequence ID returned by DecompressSequenceBegin |
GetPixBaseAddr(GetGWorldPixMap(theMungData->pGWorld)), // pointer to compressed image data |
0, // in flags |
&ignore, // out flags |
NULL); // async completion proc |
} |
bail: |
return err; |
} |
int main(void) |
{ |
SeqGrabComponent seqGrab = NULL; |
SGChannel sgchanVideo = NULL; |
Rect theRect = {0, 0, 240, 320}; |
WindowRef pWindow = NULL; |
OSErr err = noErr; |
// initialize for Carbon & QuickTime. |
InitCursor(); |
EnterMovies(); |
// create a new window |
CreateNewWindow(kDocumentWindowClass, kWindowNoAttributes, &theRect, &pWindow); |
if (pWindow == NULL ) goto bail; |
SetWTitle(pWindow, "\pMiniMung"); |
MoveWindow(pWindow,50,50,true); |
ShowWindow(pWindow); |
// initialize the data structure we use with the |
// sequence grabber data proc |
err = InitializeMungData(theRect, pWindow); |
BailErr(err); |
// open the sequence grabber component and initialize it |
seqGrab = OpenDefaultComponent(SeqGrabComponentType, 0); |
err = SGInitialize(seqGrab); |
BailErr(err); |
// 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); |
BailErr(err); |
// it wants a port, even if its not drawing to it |
err = SGSetGWorld(seqGrab, GetWindowPort(pWindow), GetMainDevice()); |
BailErr(err); |
// create a new sequence grabber video channel |
err = SGNewChannel(seqGrab, VideoMediaType, &sgchanVideo); |
BailErr(err); |
// set the bounds for the channel |
err = SGSetChannelBounds(sgchanVideo, &theRect); |
BailErr(err); |
// set the usage for our new video channel to avoid playthrough |
// note: we do not set seqGrabPlayDuringRecord because if you set this flag |
// the data from the channel may be played during the record operation, |
// if the destination buffer is onscreen. However, playing the |
// data may affect the quality of the recorded sequence by causing frames |
// to be dropped...something we definitely want to avoid |
err = SGSetChannelUsage(sgchanVideo, seqGrabRecord); |
BailErr(err); |
// specify a data function for use by the sequence grabber |
// whenever any channel assigned to the sequence grabber writes data, |
// this data function is called and may then write the data to another destination |
err = SGSetDataProc(seqGrab,NewSGDataUPP(mySGDataProc),(long)gpMungData); |
BailErr(err); |
// i'm ready for my closeup! |
err = SGStartRecord(seqGrab); |
BailErr(err); |
while(!Button() && !err) |
// give the sequence grabber time to do it's thing |
err = SGIdle(seqGrab); |
FlushEvents(mDownMask, 0); |
bail: |
if (err) { |
char errMsg[32]; |
KeyMap theKeys; |
#define ISESCKEYDOWN() ((theKeys[1] & 0x00002000) == 0x00002000) |
// if there is an error, display the result |
sprintf(errMsg, "ESC to quit Err: %d", err); |
c2pstrcpy((unsigned char *)&errMsg, errMsg); |
SetWTitle(pWindow, (unsigned char *)errMsg); |
do { |
GetKeys(theKeys); |
} while (!ISESCKEYDOWN()); |
} |
HideWindow(pWindow); |
if(seqGrab) { |
// stop and close the grabber |
SGStop(seqGrab); |
CloseComponent(seqGrab); |
} |
DisposeMungData(); |
if(pWindow) |
DisposeWindow(pWindow); |
return 0; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14