|
IntroductionThe overall procedure of decompression single DV frames from a buffer, to an RGB offscreen, then accessing the pixels directly is straightforward, especially if you don't care about scheduled decompression. However, there are a couple of different ways to go about it. You could for example, treat each frame as a single image, and use A more efficient method is the use of a Decompression Sequence. The Image Compression Manager provides a set of APIs allowing developers to decompress sequences of images sharing a common image description. Each image in the sequence is referred to as a frame. A decompression sequence is started by calling Getting StartedDetermine where to decompress the image, build an image description describing the source data, decide how much of the image to decompress, and build a mapping matrix for the operation. These parameters must be specified in the call to The destination is specified as a graphics port and the image source size is described by a rectangle, in the coordinate system of the source image. IMPORTANT: If your code passes in a smaller (eg. quarter-size) source rectangle with the intention of drawing cropped unscaled DV to a smaller (eg. quarter-size) destination, a DV-specific bug in QuickTime 5 will cause this decompression request to be misinterpreted, scaling the frame to fit. The correct interpretation of this request is to draw the top-left corner of the DV frame cropped at normal size. This bug will be fixed in QuickTime 6. If your code was behaving as intended because of this bug, make sure to fix your code to use a matrix in the call to You can figure out the size of the source image by examining the image description structure associated with the image or if you are intimately familiar with the image data you can create the image description yourself. The image description structure contains information defining the characteristics of the compressed image or sequence. One image description structure may be associated with one or more compressed frames. See Listing 1. For a detailed discussion regarding the representation of uncompressed Y´CbCr video in an Image Description structure, including Image Description Extensions refer to Ice Floe #19. The Image DescriptionCreate an Image Description for your compressed data. See Listing 2. struct ImageDescription { long idSize; // total size of ImageDescription including extra data // (CLUTs and other per sequence data) CodecType cType; // what kind of codec compressed this data long resvd1; // reserved for Apple use short resvd2; // reserved for Apple use short dataRefIndex; // set to zero short version; // which version is this data short revisionLevel; // what version of that codec did this long vendor; // whose codec compressed this data CodecQ temporalQuality; // what was the temporal quality factor CodecQ spatialQuality; // what was the spatial quality factor short width; // how many pixels wide is this data short height; // how many pixels high is this data Fixed hRes; // horizontal resolution Fixed vRes; // vertical resolution long dataSize; // if known, the size of data for this // image descriptor short frameCount; // number of frames this description // applies to Str31 name; // name of codec ( in case not installed ) short depth; // what depth is this data (1-32) or // (33-40 grayscale) short clutID; // clut id or if 0 clut follows // or -1 if no clut }; // Create an Image Description describing the compressed data, // you are responsible for disposing of the returned handle. // Modify the Image description values to deal with any other // compressed data formats - for example PAL (kDVCPALCodecType) ImageDescriptionHandle MakeImageDescriptionForNTSCDV(void) { ImageDescriptionHandle hImageDescription = NULL; hImageDescription = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription)); if (NULL != hImageDescription) { (**hImageDescription).idSize = sizeof(ImageDescription); (**hImageDescription).cType = kDVCNTSCCodecType; // DV has no temporalQuality (**hImageDescription).temporalQuality = 0; (**hImageDescription).spatialQuality = codecNormalQuality; (**hImageDescription).width = 720; (**hImageDescription).height = 480; (**hImageDescription).hRes = 72 << 16; (**hImageDescription).vRes = 72 << 16; (**hImageDescription).frameCount = 1; (**hImageDescription).depth = 24; (**hImageDescription).clutID = -1; } return hImageDescription; } The OffscreenUse Make a 32-bit RGB offscreen. See Listing 4. OSErr QTNewGWorld( GWorldPtr *offscreenGWorld, // on return, a pointer to the GWorld OSType PixelFormat; // the new GWorlds pixel format const Rect *boundsRect, // boundary and port rectangle CTabHandle cTable, // a ColorTable - NULL for default GDHandle aGDevice, // a GDevice - set to NULL GWorldFlags flags); // flags - set to 0 for default // Make a 32bit GWorld for the decompression destination - // - 720 x 480 for DV. This function locks the pixel map OSErr MakeGWorld(short inWidth, short inHeight, GWorldPtr *outDestGWorld) { Rect theBounds = {0, 0}; OSErr err = noErr; theBounds.right = inWidth; theBounds.bottom = inHeight; *outDestGWorld = NULL; err = QTNewGWorld(outDestGWorld, // return a pointer to // the offscreen k32ARGBPixelFormat, // the new GWorlds pixel // format &theBounds, // boundry and port rect // for the offscreen PixMap NULL, // handle to a ColorTable NULL, // handle to a GDevice 0); // flags if (noErr == err) // call LockPixels to prevent the base address for // an offscreen pixel image from being moved while you // draw into or copy from its pixel map LockPixels(GetGWorldPixMap(*outDestGWorld)); return err; } Setting up the SequencePerform the setup required to decompress an image sequence by calling the Image Compression Manager's The Start a decompression sequence. See Listing 6. OSErr DecompressSequenceBeginS( ImageSequence *seqID, // returns a unique seqID ImageDescriptionHandle desc, // description of // compressed data Ptr data, // pointer to the compressed // data for preflight long dataSize, // size of the data buffer. CGrafPtr port, // the destination port GDHandle gdh, // GDevice for the destination const Rect *srcRect, // portion of image to // decompress MatrixRecordPtr matrix, // transformation to apply // during decompress short mode, // graphics transfer mode // for the operation RgnHandle mask, // mask applied during // decompression CodecFlags flags, // intermediate buffer // allocation flags CodecQ accuracy // desired accuracy // for the operation DecompressorComponent codec); // decompressor to use - // - can be special identifier // Signal the beginning of the process of decompressing a // sequence of frames Using codecHighQuality for the CodecQ // parameter tells the decompressor to render at the highest // image quality that can be achieved with reasonable // performance. Using a lower CodecQ setting may be useful // is speed is the priority. OSErr MakeDecompressionSequence( ImageDescriptionHandle inImageDescription, GWorldPtr inDestGWorld, ImageSequence *outSeqID) { Rect theSrcBounds = {0, 0}; Rect theDestBounds; MatrixRecord rMatrix; *outSeqID = 0; if (NULL == inImageDescription) return paramErr; // *** IMPORTANT NOTE DV SOURCE ONLY *** // If your code passes in a smaller (eg. quarter-size) source // rectangle with the intention of drawing cropped unscaled DV to a // smaller (eg. quarter-size) destination, a DV-specific bug in // QuickTime 5 will cause this decompression request to be // misinterpreted, scaling the frame to fit. The correct // interpretationof this request is to draw the top-left corner // of the DV frame cropped at normal size. This bug will be // fixed in QuickTime 6. If your code was behaving as intended // because of this bug, make sure to fix your code to use a // matrix in the call to DecompressSequenceBeginS, scaling the // frame to fit the offscreen gworld. This approach will work // in all versions of QuickTime. // ************************************* // create a transformation matrix to scale from the source bounds // to the destination bounds. Using NULL for the source rectangle // in the call to DecompressSequenceBeginS indicates we want to // decompress the entire source image GetPortBounds(inDestGWorld, &theDestBounds); theSrcBounds.right = (*inImageDescription)->width; theSrcBounds.bottom = (*inImageDescription)->height; RectMatrix(&rMatrix, &theSrcBounds, &theDestBounds); return DecompressSequenceBeginS( outSeqID, // pointer to field to receive // unique ID for sequence inImageDescription, // handle to image description // structure NULL, // pointer to compressed image // data (used // for preflight) 0, // image data size inDestGWorld, // port for the DESTINATION image NULL, // grahics device handle, if // port is set, set // this to NULL NULL, // source rectangle defining // the portion of the image // to decompress - NULL for // the entire source image &rMatrix, // transformation matrix srcCopy, // transfer mode specifier (RgnHandle)NULL, // clipping region in dest. // coordinate system to use as // a mask 0, // flags codecHighQuality, // accuracy in decompression anyCodec); // compressor identifier or // special identifiers // ie. bestSpeedCodec } Use the Decompressing a FrameOnce the sequence has started, each frame in the sequence is queued up for decompression by calling OSErr DecompressSequenceFrameWhen( ImageSequence seqID, // unique segID Ptr data, // pointer to // compressed data long dataSize, // size of the data // buffer CodecFlags inFlags, // control flags CodecFlags *outFlags, // status flags ICMCompletionProcRecordPtr asyncCompletionProc, // async completion // proc record const ICMFrameTimeRecord *frameTime); // frame time // information The Image Compression Manager manages the decompression operation, calls the appropriate codec component to do the work and the frame is decompressed to the location specified in the Decompress a frame. See Listing 8. // A simple wrapper around DecompressSequenceFrameWhen // Ignores in and out flags, no completion proc or // frameTime specified - decompression operation will // happen immediately OSErr DecompressFrameNow(ImageSequence inSequenceID, Ptr inBuffer, long inBufferSize) { return DecompressSequenceFrameWhen(inSequenceID, inBuffer, inBufferSize, 0, NULL, NULL, NULL); } // You could use a structure like this to keep track of per // frame information typedef struct { ImageSequence seqID; // decompression // sequence ID Ptr pSrcBuffer; // pointer to // compressed data long bufSize; // compressed image // data size ICMCompletionProcRecordPtr pCompletionProc; // pointer to a // ICMCompletionProcRecord Boolean isDestDone; // is the ICM done // with the dest? OSErr rc; // return code } FrameRecord, *FrameRecordPtr, **FrameRecordHdl; // Another way to wrap DecompressSequenceFrameWhen // FrameRecordPtr assumes some type of data structure // associated with your application which keeps track // of per frame information OSErr DecompressFrame(FrameRecordPtr inFrame) { if (inFrame->pCompletionProc) { // if we set up a completion proc, store the frame // so it can be pulled out when we get called inFrame->pCompletionProc->completionRefCon = (long)inFrame; } return DecompressSequenceFrameWhen(inFrame->seqID, inFrame->pSrcBuffer, inFrame->bufSize, 0, NULL, inFrame->pCompletionProc, NULL); } Asynchronous DecompressionDecompression can be performed asynchronously by specifying a completion function along with a RefCon in a The completion function may be called multiple times and must be interrupt safe. Passing in Additionally, // Specifies an image compression completion callback struct ICMCompletionProcRecord { ICMCompletionUPP completionProc; // UPP accessing your // ICMCompletionProc long completionRefCon; // refcon for use by // the callback }; typedef struct ICMCompletionProcRecord ICMCompletionProcRecord; typedef ICMCompletionProcRecord * ICMCompletionProcRecordPtr; // Called by a compressor component upon completion // of an asynchronous operation typedef void (*ICMCompletionProcPtr) (OSErr result, short flags, long refcon); void MyICMCompletionProc( OSErr result // result of current operation short flags // flags indicating which part // of the operation is complete long refcon); // refcon specified in the // ICMCompletionProcRecord // Contains a frame's time information for scheduled // asynchronous decompression operations. struct ICMFrameTimeRecord { wide value; // time the frame is to be displayed long scale; // units for the frame's display time void * base; // the time base long duration; // duration the frame is displayed // must be in the same units as // specified by the scale field. // 0 if duration is unknown Fixed rate; // the time base's effective rate long recordSize; // size of this structure long frameNumber; // 0 if the frame number is not known long flags; // flags wide virtualStartTime; // conceptual start time long virtualDuration; // conceptual duration }; // Sample ICM decompression completion procedure // This procedure simply checks the status of the codecCompletionDest // flag which indicates that the Image Compression Manager is done // with the destination buffer FrameRecordPtr assumes some type of // data structure associated with your application which keeps // track of per frame information // Note: This function may be called multiple times and must be // interrupt safe static pascal void DecompressionDone(OSErr inResult, short inFlags, long inRefCon) { FrameRecordPtr theFrame = (FrameRecordPtr)inRefCon; if (noErr == inResult) { if (codecCompletionDest & inFlags) { // the ICM is done with the destination theFrame->isDestDone = true; ... } } theFrame->rc = inResult; } Accessing the PixelsTo access the pixels directly, use
Remember to always call // You could use a structure like this for convenience // to cast the 32bit pixel map as an array of pixels. typedef struct { UInt8 alpha; // alpha component UInt8 red; // red component UInt8 green; // green component UInt8 blue; // blue component } ARGBPixelRecord, *ARGBPixelPtr, **ARGBPixelHdl; PixMapHandle hPixMap = GetGWorldPixMap(myDestGWorld); long theRowBytes = QTGetPixMapHandleRowBytes(hPixMap); Ptr pPixels = GetPixBaseAddr(hPixMap); Sequence setup function. See Listing 15. Ending the SequenceAfter the entire sequence is decompressed, end the process by calling the Sequence end function. See Listing 16. OSErr CDSequenceEnd(ImageSequence seqID); // Sample function demonstrating how one might go about setting up a // decompression sequence // Long winded assignments are used for the purpose of this sample // MyAppObjectPtr assumes some type of data structure associated with // your application which keeps track of all the important bits OSErr SetupDecompressionSequenceForDV(MyAppObjectPtr inAppObject) { ImageDescriptionHandle hImageDescription = NULL; GWorldPtr theGWorld = NULL; ImageSequence theSequenceID = 0; PixMapHandle hPixMap = NULL; long theRowBytes = 0; Ptr pPixels = NULL; OSErr err = noErr; hImageDescription = MakeImageDescriptionForDV(); err = MemError(); if (hImageDescription) { // the GWorld does not have to be the exact same size as // the compressed image err = MakeGWorld(&theGWorld, (*desc)->width, (*desc)->height); if (err) goto bail; err = MakeDecompressionSequence(hImageDescription, theGWorld, &theSequenceID); if (err) goto bail; // get the BaseAddress and RowBytes for the pixels hPixMap = GetGWorldPixMap(theGWorld); pPixels = GetPixBaseAddr(hPixMap); theRowBytes = QTGetPixMapHandleRowBytes(hPixMap); // this is what we need inAppObject->dSeqID = theSequenceID; inAppObject->pGWorld = theGWorld; inAppObject->pPixels = pPixels; inAppObject->rowBytes = theRowBytes; } bail: if (hImageDescription) DisposeHandle((Handle)hImageDescription); return err; } // Sample function demonstrating how one might go about ending // a decompression sequence MyAppObjectPtr assumes some type of // data structure associated with your application which keeps // track of all the important bits OSErr EndDecompressionSequenceForDV(MyAppObjectPtr inAppObject) { OSErr err = noErr; // end the decompression sequence if (0 != inAppObject->dSeqID) { err = CDSequenceEnd(inAppObject->dSeqID); inAppObject->dSeqID = 0; } // dispose of the GWorld if (NULL != inAppObject->pGWorld) DisposeGWorld(inAppObject->pGWorld); inAppObject->pGWorld = NULL; inAppObject->pPixels = NULL; inAppObject->rowBytes = 0; return err; } ReferencesDocument Revision History
Posted: 2002-04-11 |
|