This chapter shows you how to use compressors and decompressors in conjunction with the Image Compression Manager.
Performing Image Compression
Decompressing an Image
Asynchronous Decompression
Hardware Cursors
Timecode Support
Working With Video Fields
Accelerated Video Support
Packetization Information
DV Image Compressor Component
DV Image Decompressor Component
Specifying the Size of an Image Buffer
This section describes what the Image Compression Manager does that affects compressors. It then provides sample code that shows how the compressor components prepare for image compression and how to compress an entire image or a horizontal band of an image.
When compressing an image, the Image Compression Manager performs three major tasks:
The Image Compression Manager first determines which compressor is best able to compress the image. To do so, the Image Compression Manager examines the source image as well as the parameters specified by the application. If the application requested a specific compressor, the Image Compression Manager uses that compressor (unless it is not installed, in which case the Image Compression Manager returns an error to the application). If the application did not request a compressor, the Image Compression Manager chooses the compressor that will do the best job. The Image Compression Manager collects the information it needs to choose a compressor by issuing the ImageCodecPreCompress
request to each qualifying compressor.
If the chosen compressor can handle the image directly, the Image Compression Manager passes the request through to the compressor. The compressor then processes the image and returns the compressed data to the specified location.
If none of the compressors can handle it directly, the Image Compression Manager allocates an offscreen buffer and passes image bands to the compressor by issuing a ImageCodecBandCompress
request. The compressor processes each band, accumulating the compressed data as it goes. When the image has been completely compressed, the Image Compression Manager returns control to the application.
Listing 9-1 shows how the Image Compression Manager calls the ImageCodecPreCompress
function before an image is compressed. The compressor component returns information about how it is able to compress the image to the Image Compression Manager, so that it can fit the destination data to the requirements of the compressor component. This information includes compressor capabilities for
depth of input pixels
minimum buffer band size
band increment size
extension width and height
When your compressor component is called with the ImageCodecPreCompress
function, it can handle all aspects of the function itself, or only the most common ones. All image compressor components must handle at least one case.
Listing 9-1 Preparing for simple compression operations
pascal long ImageCodecPreCompress (Handle storage, |
register CodecCompressParams *p) |
{ |
CodecCapabilities *capabilities = p->capabilities; |
/* |
First the compressor returns which depth input pixels it |
supports based on what the application has available. This |
compressor can only work with 32-bit input pixels. |
*/ |
switch ( (*p->imageDescription)->depth ) { |
case 16: |
capabilities->wantedPixelSize = 32; |
break; |
default: |
return(codecConditionErr); |
break; |
} |
/* |
If the buffer gets banded, return the smallest one the |
compressor can handle. |
*/ |
capabilities->bandMin = 2; |
/* |
If the buffer gets banded, return the increment |
by which it should increase. |
*/ |
capabilities->bandInc = 2; |
capabilities->extendWidth = (*p->imageDescription)->width & 1; |
capabilities->extendHeight = (*p->imageDescription)->height & 1; |
/* |
For efficiency, if the compressor could perform extension, |
these flags would be set to 0. |
*/ |
return(noErr); |
} |
Here is a list of some of the operations your compressor component can perform during compression. It describes parameters in the compression parameters structure and indicates the operations that are required and which flags in the compressor capabilities flags field of the compressor capabilities structure must be set to allow your compressor to handle them (see Data Structures and Data Structures).
Depth conversion. If your compressor component can compress from the pixel depth indicated by the pixelSize
field (in the pixel map structure pointed to by the srcPixmap
field of the compression parameters structure), it should set the wantedPixelSize
field of the compressor capability structure to the same value. If it cannot handle that depth, it should specify the closest depth it can support in the wantedPixelSize
field. The Image Compression Manager will convert the source image to that depth.
Extension. If the format for the compressed data is block oriented, the compressor component can request that the Image Compression Manager allocate a buffer that is a multiple of the proper block size by setting the extendWidth
and extendHeight
parameters of the compressor capability structure. The new pixels are replicated from the left and bottom edges to fill the extended area. If your compressor can perform this extension itself, it should leave the extendWidth
and extendHeight
fields set to 0. In this case, the Image Compression Manager can avoid copying the source image to attain more efficient operation.
Pixel shifting. For pixel sizes less than 8 bits per pixel, it may be necessary to shift the source pixels so that they are at an aligned address. If the pixelSize
field of the source pixel map structure is less than 8, and your compressor component handles that depth directly, and the left address of the image (srcRect
.left
-
srcPixMap
.bounds
.left
) is not aligned and your compressor component can handle these pixels directly, then it should set the codecCanShift
flag in the flags
field of the compressor capabilities structure. If your compressor component does not set this flag, then the data will be copied to a buffer with the image shifted so the first pixel is in the most significant bit of an aligned long-word address.
Updating previous pixel maps. Compressors that perform temporal compression may keep their own copy of the previous frame’s pixel map, or they may update the previous frame’s pixel map as they perform the compression. In these cases, the compressor component should set the codecCanCopyPrev
flag if it updates the previous pixel map with the original data from the current frame, or it should set the codecCanCopyPrevComp
flag if it updates the previous pixel map with a compressed copy of the current frame.
Listing 9-2 shows how the Image Compression Manager calls the ImageCodecBandCompress
function when it wants the compressor to compress a horizontal band of an image.
Note: This example does not perform compression on bands with a bit depth of more than 1 or an extension of width and height. If the example did do so, it would handle these cases faster.
Listing 9-2 Performing simple compression on a horizontal band of an image
pascal long ImageCodecBandCompress (Handle storage, |
register CodecCompressParams *p) |
{ |
short width,height; |
Ptr cDataPtr,dataStart; |
short depth; |
Rect sRect; |
long offsetH,offsetV; |
Globals **glob = (Globals **)storage; |
register char *baseAddr; |
long numLines,numStrips; |
short rowBytes; |
long stripBytes; |
char mmuMode = 1; |
register short y; |
ImageDescription **desc = p->imageDescription; |
OSErr result = noErr; |
/* |
If there is a progress function, give it an open call at |
the start of this band. |
*/ |
if (p->progressProcRecord.progressProc) |
p->progressProcRecord.progressProc (codecProgressOpen, 0, |
p->progressProcRecord.progressRefCon); |
width = (*desc)->width; |
height = (*desc)->height; |
depth = (*desc)->depth; |
dataStart = cDataPtr = p->data; |
/* |
Figure out offset to first pixel in baseAddr from the |
pixel size and bounds. |
*/ |
rowBytes = p->srcPixMap.rowBytes; |
sRect = p->srcPixMap.bounds; |
numLines = p->stopLine - p->startLine; /* number of scan lines */ |
numStrips = (numLines+1)>>1; /* number of strips in */ |
stripBytes = ((width+1)>>1) * 5; |
/* |
Adjust the source baseAddress to be at the beginning |
of the desired rect. |
*/ |
switch ( p->srcPixMap.pixelSize ) { |
case 32: |
offsetH = sRect.left<<2; |
break; |
case 16: |
offsetH = sRect.left<<1; |
break; |
case 8: |
offsetH = sRect.left; |
break; |
/* |
This compressor does not handle the other cases directly. |
*/ |
default: |
result = codecErr; |
goto bail; |
} |
offsetV = sRect.top * rowBytes; |
baseAddr = p->srcPixMap.baseAddr + offsetH + offsetV; |
/* |
If there is not a data-unloading function, |
adjust the pointer to the next band. |
*/ |
if ( p->flushProcRecord.flushProc == nil ) { |
cDataPtr += (p->startLine>>1) * stripBytes; |
} |
else { /* |
Make sure the compressor can deal with the |
data-unloading function in this case. |
*/ |
if ( p->bufferSize < stripBytes ) { |
result = codecSpoolErr; |
goto bail; |
} |
} |
/* |
Perform the slower data-loading or progress operation, as |
required. |
*/ |
if ( p->flushProcRecord.flushProc || |
p->progressProcRecord.progressProc ) { |
SharedGlobals *sg = (*glob)->sharedGlob; |
for ( y=0; y < numStrips; y++) { |
SwapMMUMode(&mmuMode); |
CompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); |
SwapMMUMode(&mmuMode); |
baseAddr += rowBytes<<1; |
if ( p->flushProcRecord.flushProc ) { |
if ( (result= |
p->flushProcRecord.flushProc(cDataPtr,stripBytes, |
p->flushProcRecord.flushRefCon)) != noErr) { |
result = codecSpoolErr; |
goto bail; |
} |
} else { |
cDataPtr += stripBytes; |
} |
if (p->progressProcRecord.progressProc) { |
if ( (result= |
p->progressProcRecord.progressProc) |
codecProgressUpdatePercent, |
FixDiv(y,numStrips), |
p->progressProcRecord.progressRefCon) |
) != noErr ) { |
result = codecAbortErr; |
goto bail; |
} |
} |
} |
} else { |
SharedGlobals *sg = (*glob)->sharedGlob; |
short tRowBytes = rowBytes<<1; |
SwapMMUMode(&mmuMode); |
for ( y=numStrips; y--; ) { |
CompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); |
cDataPtr += stripBytes; |
baseAddr += tRowBytes; |
} |
SwapMMUMode(&mmuMode); |
} |
} |
When decompressing an image, the Image Compression Manager performs these three major tasks:
The Image Compression Manager first determines which decompressor is best able to decompress the image. To do so, the Image Compression Manager examines the source image as well as the parameters specified by the application. If the application requested a specific decompressor, the Image Compression Manager uses that decompressor (unless it is not installed, in which case the Image Compression Manager returns an error to the application). If the application did not request a decompressor, the Image Compression Manager chooses the decompressor that will do the best job. The Image Compression Manager collects the information it needs to choose a decompressor by issuing the ImageCodecPreDecompress
request to each qualifying decompressor.
If the chosen decompressor can handle the image directly, the Image Compression Manager passes the request through to the decompressor. The decompressor then processes the image and returns the image to the specified location.
If none of the decompressors can handle all of the conditions (matrix mapping, masking or matting, depth conversion, and so on) the Image Compression Manager allocates an offscreen buffer and passes image bands to the decompressor at a depth that the decompressor can handle by issuing a ImageCodecBandDecompress
request. The decompressor processes each band, building the image as it goes. When the image has been completely decompressed, the Image Compression Manager returns control to the application.
Listing 9-3 provides an example of how a decompressor is chosen. The Image Compression Manager calls the ImageCodecPreDecompress
function before an image is decompressed. The decompressor returns information about how it can decompress an image. The Image Compression Manager can fit the destination pixel map to your decompressor’s requirements if it is not able to support decompression to the destination directly. The capability information the decompressor returns includes
depth of pixels for the destination pixel map
minimum band size handled
extension width and height required
band increment size
When your decompressor component is called with the ImageCodecPreDecompress
function, it can handle all aspects of the call itself, or only the most common ones. All decompressors must handle at least one case.
Listing 9-3 Preparing for simple decompression
pascal long ImageCodecPreDecompress( Handle storage, |
register CodecDecompressParams *p) |
{ |
register CodecCapabilities*capabilities = p->capabilities; |
RectdRect = p->srcRect; |
/* |
Check if the matrix is OK for this decompressor. |
This decompressor doesn't do anything fancy. |
*/ |
if ( !TransformRect(p->matrix,&dRect,nil) ) |
return(codecConditionErr); |
/* |
Decide which depth compressed data this decompressor can |
deal with. |
*/ |
switch ( (*p->imageDescription)->depth ) { |
case 16: |
break; |
default: |
return(codecConditionErr); |
break; |
} |
/* |
This decompressor can deal only with 32-bit pixels. |
*/ |
capabilities->wantedPixelSize = 32; |
/* |
The smallest possible band the decompressor can handle is |
2 scan lines. |
*/ |
capabilities->bandMin = 2; |
/* This decompressor can deal with 2 scan line high bands. */ |
capabilities->bandInc = 2; |
/* |
If this decompressor needed its pixels be aligned on |
some integer multiple, you would set extendWidth and |
extendHeight to the number of pixels by which you need the |
destination extended. If you don't have such requirements |
or if you take care of them yourself, you set extendWidth |
and extendHeight to 0. |
*/ |
capabilities->extendWidth = p->srcRect.right & 1; |
capabilities->extendHeight = p->srcRect.bottom & 1; |
return(noErr); |
} |
This section contains a bulleted list of some of the operations your decompressor component can perform during the decompression operation. The list describes which parameters in the decompression parameters structure indicate that the operations are required and which flags in the flags field of the compressor capabilities structure must be set to allow your decompressor to handle them (see Data Structures).
For sequences of images the conditionFlags
field in the decompression parameters structure can be used to determine which parameters may have changed since the last decompression operation. These parameters are also indicated in the bulleted list.
Since your decompressor’s capabilities depend on the full combination of parameters, it must inspect all the relevant parameters before indicating that it will perform one of the operations itself. For instance, if your decompressor has hardware that can perform scaling only if the destination pixel depth is 32 and there is no clipping, then the pre-decompression operation would have to check the following fields in the decompression parameters structure: the matrix
field, the pixelSize
field of the destination pixel map structure pointed to by the destPixMap
field, and the maskBits
fields. Only then could the decompressor decide whether to set the codecCanScale
flag in the capabilities
field of the decompression parameters structure.
Scaling. The decompressor component can look at the matrix and selectively decide which scaling operations it wishes to handle. If the scaling factor specified by the matrix is not unity and your decompressor can perform the scaling operation, it must set the codecCanScale
flag in the capabilities
field. If it does not, then the decompressor is asked to decompress without scaling, and the Image Compression Manager performs the scaling operation afterward.
Depth conversion. If your component can decompress to the pixel depth indicated by the pixelSize
field (of the pixel map structure pointed to by the dstPixmap
field of the decompression parameters structure), it should set the wantedPixelSize
field of the compressor capability structure to the same value. If it cannot handle that depth, it should specify the closest depth it can handle in the wantedPixelSize
field.
Dithering. When determining whether depth conversion can be performed (for converting an image to a lower bit depth, or to a similar bit depth with a different color table), dithering may be required. This is specified by the dither bit in the transferMode
field (0x40
) of the decompression parameters structure being set. The accuracy
field of the decompression parameters structure indicates whether fast dithering is acceptable (accuracy
less than or equal to codecNormalQuality
) or whether true error diffusion dithering should be used (accuracy
greater than codecNormalQuality
). Most decompressors do not perform true error diffusion dithering, although they can. When a decompressor cannot perform the dither operation, it should specify the higher bit depth in the wantedPixelSize
field of the compressor capability structure and let the Image Compression Manager perform the depth conversion with dithering. Dithering to 16-bit destinations is normally done only if the accuracy
field is set to the codecNormalQuality
value. However, if your decompressor component can perform dithering fast enough, it could be performed at the lower accuracy settings as well. To indicate that your decompressor can perform dithering as specified, it should set the codecCanTransferMode
flag in the capabilities
field of the decompression parameters structure.
Color remapping. If the compressed data has an associated color lookup table that is different from the color lookup table of the destination pixel map, then the decompressor can remap the color indices to the closest available ones in the destination itself, or it can let the Image Compression Manager do the remapping. If the decompressor can do the mapping itself, it should set the codecCanRemap
flag in the capabilities
flags field of the decompression parameters structure.
Extending. If the format for the compressed data is block-oriented, the decompressor can ask that the Image Compression Manager to allocate a buffer which is a multiple of the proper block size by setting the extendWidth
and extendHeight
fields of the compressor capabilities structure. If the right and bottom edges of the destination image (as determined by the transformed srcRect
and dstPixMap
.bounds
fields of the decompression parameters structure) are not a multiple of the block size that your decompressor handles, and your decompressor cannot handle partial blocks (writing only the pixels that are needed for blocks that cross the left or bottom edge of the destination), then your decompressor component must set the extendWidth
and extendHeight
fields in the compressor capabilities structure. In this case, the Image Compression Manager creates a buffer large enough so that no partial blocks are needed. Your component can decompress into that buffer. This is then copied to the destination by the Image Compression Manager. Your component can avoid this extra step if it can handle partial blocks. In this case, it should leave the extendWidth
and extendHeight
fields set to 0.
Clipping. If clipping must be performed on the image to be decompressed, the maskBits
field of the decompression parameters structure is nonzero. In the ImageCodecPreDecompress
function, it will be a region handle to the actual clipping region. If your decompressor can handle the clipping operation as specified by this region, it should set the codecCanMask
bit in the capabilities
flags field of the decompression parameters structure. If it does this, then the parameter passed to the ImageCodecBandDecompress
function in the maskBits
field will be a bitmap
instead of a region. If desired, your decompressor can save a copy of the actual region structure during the pre-decompression operation.
Matting. If a matte must be applied to the decompressed image, the transferMode
field of the decompression parameters structure is set to blend and the mattePixMap
field is a handle to the pixel map to be used as the matte. If your decompressor can perform the matte operation, then it should set the codecCanMatte
field in the compressor capabilities structure. If it does not, then the Image Compression Manager will perform the matte operation after the decompression is complete.
Pixel shifting. For pixel sizes less than 8 bits per pixel, it may be necessary to shift the destination pixels so that they are at an aligned address. If the pixel size of the destination pixel map is less than 8 and your component handles that depth directly, and the left address of the image is not aligned and your component can handle these pixels directly, then it should set the codecCanShift
flag in the capabilities
field of the decompression parameters structure. If your component does not set this flag, the Image Compression Manager allocates a buffer for and performs the shifting after the decompression is completed.
Partial extraction. If the source rectangle is not the entire image and the component can decompress only the part of the image specified by the source rectangle, it should set the codecCanSrcExtract
flag in the capabilities
field of the decompression parameters structure. If it does not, the Image Compression Manger asks the component to decompress the entire image and copy only the required part to the destination.
Listing 9-4 shows how to decompress the horizontal band of an image. The Image Compression Manager calls the ImageCodecBandDecompress
function when it wants a decompressor to decompress an image or a horizontal band of an image. The pixel data indicated by the baseAddr
field is guaranteed to conform to the criteria your decompressor specified in the ImageCodecPreDecompress
function.
Note: This example does not perform decompression on bands with a bit depth of more than one or an extension of width and height. If the example did do so, it would handle these cases faster.
Listing 9-4 Performing a decompression operation
pascal long ImageCodecBandDecompress( Handle storage, |
register CodecDecompressParams *p) |
{ |
Rect dRect; |
long offsetH,offsetV; |
Globals **glob = (Globals **)storage; |
long numLines,numStrips; |
short rowBytes; |
long stripBytes; |
short width; |
register short y; |
register char* baseAddr; |
char *cDataPtr; |
char mmuMode = 1; |
OSErr result = noErr; |
/* |
Calculate the real base address based on the boundary |
rectangle. If it's not a linear transformation, this |
decompressor does not perform the operation. |
*/ |
dRect = p->srcRect; |
if ( !TransformRect(p->matrix,&dRect,nil) ) |
return(paramErr); |
/* If there is a progress function, give it an open call at |
the start of this band. |
*/ |
if (p->progressProcRecord.progressProc) |
p->progressProcRecord.progressProc(codecProgressOpen,0, |
p->progressProcRecord.progressRefCon); |
/* |
Initialize some local variables. |
*/ |
width = (*p->imageDescription)->width; |
rowBytes = p->dstPixMap.rowBytes; |
numLines = p->stopLine - p->startLine; /* number of scan lines |
in this band */ |
numStrips = (numLines+1)>>1; /* number of strips in |
this band */ |
stripBytes = ((width+1)>>1) * 5; /* number of bytes in |
1 strip of blocks */ |
cDataPtr = p->data; |
/* |
Adjust the destination base address to be at the beginning |
of the desired rectangle. |
*/ |
offsetH = (dRect.left - p->dstPixMap.bounds.left); |
switch ( p->dstPixMap.pixelSize ) { |
case 32: |
offsetH <<=2; /* 1 pixel = 4 bytes */ |
break; |
case 16: |
offsetH <<=1; /* 1 pixel = 2 bytes */ |
break; |
case 8: |
break; /* 1 pixel = 1 byte */ |
default: |
result = codecErr; /* This decompressor doesn't handle |
these cases, although it could. */ |
goto bail; |
} |
offsetV = (dRect.top - p->dstPixMap.bounds.top) * rowBytes; |
baseAddr = p->dstPixMap.baseAddr + offsetH + offsetV; |
/* |
If your decompressor component is skipping some data, |
it just skips it here. You can tell because |
firstBandInFrame indicates this is the first band for a new |
frame, and if startLine is not 0, then that many lines were |
clipped out. |
*/ |
if ( (p->conditionFlags & codecConditionFirstBand) && |
p->startLine != 0 ) { |
if ( p->dataProcRecord.dataProc ) { |
for ( y=0; y < p->startLine>>1; y++ ) { |
if ( (result=p->dataProcRecord.dataProc |
(&cDataPtr,stripBytes, |
p->dataProcRecord.dataRefCon)) != noErr ) { |
result = codecSpoolErr; |
goto bail; |
} |
cDataPtr += stripBytes; |
} |
} else |
cDataPtr += (p->startLine>>1) * stripBytes; |
} |
/* |
If there is a data-loading function spooling the data to your |
decompressor, then you have to decompress the data in the |
chunk size that is specified, or, if there is a progress |
function, you must make sure to call it as you go along. |
*/ |
if ( p->dataProcRecord.dataProc || |
p->progressProcRecord.progressProc ) { |
SharedGlobals *sg = (*glob)->sharedGlob; |
for (y=0; y < numStrips; y++) { |
if (p->dataProcRecord.dataProc) { |
if ( (result=p->dataProcRecord.dataProc |
(&cDataPtr,stripBytes, |
p->dataProcRecord.dataRefCon)) != noErr ) { |
result = codecSpoolErr; |
goto bail; |
} |
} |
SwapMMUMode(&mmuMode); |
DecompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); |
SwapMMUMode(&mmuMode); |
baseAddr += rowBytes<<1; |
cDataPtr += stripBytes; |
if (p->progressProcRecord.progressProc) { |
if ( (result=p->progressProcRecord.progressProc |
(codecProgressUpdatePercent, |
FixDiv(y, numStrips), |
p->progressProcRecord.progressRefCon)) != noErr ) { |
result = codecAbortErr; |
goto bail; |
} |
} |
} |
/* |
Otherwise, do the fast case. |
*/ |
} else { |
SharedGlobals *sg = (*glob)->sharedGlob; |
shorttRowBytes = rowBytes<<1; |
SwapMMUMode(&mmuMode); |
for ( y=numStrips; y--; ) { |
DecompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); |
baseAddr += tRowBytes; |
cDataPtr += stripBytes; |
} |
SwapMMUMode(&mmuMode); |
} |
/* |
IMPORTANT: Update the pointer to data in the decompression |
parameters structure, so that when your decompressor gets the |
next band, you'll be at the right place in your data. |
*/ |
p->data = cDataPtr; |
if ( p->conditionFlags & codecConditionLastBand ) { |
/* |
Tie up any loose ends on the last band of the frame. |
*/ |
} |
bail: |
/* |
If there is a progress function, give it a close call |
at the end of this band. |
*/ |
if (p->progressProcRecord.progressProc) |
p->progressProcRecord.progressProc(codecProgressClose,0, |
p->progressProcRecord.progressRefCon); |
return(result); |
} |
The Image Compression Manager (ICM) supports scheduled asynchronous decompression operations. By calling the Image Compression Manager function DecompressSequenceFrameWhen
, applications can schedule decompression requests in advance. This allows decompressor components that support this functionality to provide reliable playback performance under a wider range of conditions.
The Apple Cinepak, Video, Animation, Component Video, and Graphics decompressors provided in QuickTime support scheduled asynchronous decompression to 8-, 16-, and 32-bit destinations (the Cinepak decompressor also supports 4-bit grayscale destinations). QuickTime also adds asynchronous decompression support to the JPEG and None decompressor components on PowerPC systems (with the QuickTime PowerPlug extension installed).
If you want to support this functionality, you must modify your decompressor component in the following ways:
Report your component’s new capabilities in its compressor capability structure by setting the codecCanAsyncWhen
and codecCanAsync
flags.
Modify your component’s ImageCodecFlush
function to accept scheduled asynchronous decompression requests and process them correctly.
Implement the new function ImageCodecFlush
; this function allows the Image Compression Manager to instruct you to empty your input queue.
Optionally, implement logic to manage the shielding of the cursor during decompression operations.
The Image Compression Manager supports hardware cursors introduced in PCI-based Macintosh computers, which eliminates cursor flicker. For all software codecs, this support requires no changes.
For codecs that manage the cursor themselves, QuickTime has a flag, codecCompletionDontUnshield
, for use when calling the ICMDecompressComplete
function. Use this flag to prevent the Image Compression Manager from unshielding the cursor when ICMDecompressComplete
is called.
QuickTime provides timecode information to decompressor components when movies are played. To support timecodes, your codec must support the function ImageCodecSetTimeCode
, which allows the Image Compression Manager to set the timecode value for the next frame to be decompressed.
The functionality of the ImageFieldSequenceExtractCombine
function is performed by individual image codecs. This is because the way in which fields are stored is different for every compression format. A codec component function, ImageCodecExtractAndCombineFields
, is defined for this purpose. Apple encourages developers of codecs to incorporate this function, if their compressed data format is capable of separately storing both fields of a video frame.
QuickTime supports codecs that accelerate certain image decompression operations. These features are most likely used by developers of video hardware boards that provide special acceleration features, such as arbitrary scaling or color space conversion.
If a codec cannot decompress directly to the screen it has the option of specifying that it can decompress to one or more types of non-RGB pixel spaces, specified as an OSType
(e.g., 'yuvs'
). The ICM then attempts to find a decompressor component of that type (a transfer codec) that can transfer the image to the screen. Since the ICM does not define non-RGB pixel types, the transfer codec must support additional calls to set up the offscreen. If a transfer codec cannot be found that supports the specified non-RGB pixel types, the ICM uses the None codec with an RGB offscreen buffer.
The real speed benefit comes from the fact that since the transfer codec defines the offscreen buffer, it can place the buffer in on-board memory, or even point to an overlay plane so that the offscreen image really is on the screen. In this case, the additional step of transferring the bits from offscreen memory on to the screen is avoided.
For an image decompressor component to indicate that it can decompress to non-RGB pixel types, it should, in the ImageCodecPreDecompress
call, fill in the wantedDestinationPixelTypes
field with a handle to a zero-terminated list of pixel types that it can decompress to. The ICM immediately makes a copy of the handle. Cinepak, for example, returns a 12-byte handle containing yuvs
, yuvu
, and $00000000. Since ImageCodecPreDecompress
can be called often, it is suggested that codecs allocate this handle when their component is opened and simply fill in the wantedDestinationPixelTypes
field with this handle during ImageCodecPreDecompress
. Components that use this method should be sure to dispose the handle at close.
Apple’s Cinepak decompressor supports decompressing to 'yuvs'
and 'yuvu'
pixel types. Type 'yuvs'
is a YUV format with u and v
components signed (center point at $00), while 'yuvu'
has the u and v
component centered at $80.
As an example, suppose XYZ Co. had a video board that had a YUV overlay plane capable of doing arbitrary scaling. The overlay plane takes data in the same format as Cinepak’s 'yuvs'
format. In this case, XYZ would make a component of type 'imdc'
and subtype 'yuvs'
.
The ImageCodecPreDecompress
call would set the codecCanScale
, codecHasVolatileBuffer
, and codecImageBufferIsOnScreen
bits in the capabilities
flags
field. The codecImageBufferIsOnScreen
bit is necessary to inform the ICM that the codec is a direct screen transfer codec. A direct screen transfer codec is one that sets up an offscreen buffer that is actually onscreen (such as an overlay plane). Not setting this bit correctly can cause unpredictable results.
The real work of the codec takes place in the ImageCodecNewImageBufferMemory
call. This is where the codec is instructed to prepare the non-RGB pixel buffer. The codec must fill in the baseAddr
and rowBytes
fields of the dstPixMap
structure in CodecDecompressParams
. The ICM then passes these values to the original codec (e.g., Cinepak) to decompress into.
The codec must also implement ImageCodecDisposeMemory
to balance ImageCodecNewImageBufferMemory
.
Since Cinepak then decompresses into the card’s overlay plane, ImageCodecBandDecompress
needs to do nothing aside from calling ICMDecompressComplete
.
pascal ComponentResult |
ImageCodecPreDecompress(Handle storage, |
CodecDecompressParams *p) |
{ |
CodecCapabilities *capabilities = p->capabilities; |
// only allow 16 bpp source |
if ((**p->imageDescription).depth != 16) |
return codecConditionErr; |
/* we only support 16 bits per pixel dest */ |
if (p->dstPixMap.pixelSize != 16) |
return codecConditionErr; |
capabilities->wantedPixelSize = p->dstPixMap.pixelSize; |
capabilities->bandInc = capabilities->bandMin = |
(*p->imageDescription)->height; |
capabilities->extendWidth = 0; |
capabilities->extendHeight = 0; |
capabilities->flags = |
codecCanScale | codecImageBufferIsOnScreen | |
codecHasVolatileBuffer; |
return noErr; |
} |
pascal ComponentResult |
ImageCodecBandDecompress(Handle storage, |
CodecDecompressParams *p) |
{ |
ICMDecompressComplete(p->sequenceID, noErr, |
codecCompletionSource | codecCompletionDest, |
&p->completionProcRecord); |
return noErr; |
} |
pascal ComponentResult |
ImageCodecNewImageBufferMemory(Handle storage, |
CodecDecompressParams *p, long flags, |
ICMMemoryDisposedUPP memoryGoneProc, |
void *refCon) |
{ |
OSErr err = noErr; |
long offsetH, offsetV; |
Ptr baseAddr; |
long rowBytes; |
// call predecompress to check to make sure we can handle |
// this destination |
err = ImageCodecPreDecompress(storage, p); |
if (err) goto bail; |
// set video board registers with the scale |
XYZVideoSetScale(p->matrix); |
// calculate a base address to write to |
offsetH = (p->dstRect.left - p->dstPixMap.bounds.left); |
offsetV = (p->dstRect.top - p->dstPixMap.bounds.top); |
XYZVideoGetBaseAddress(p->dstPixMap, offsetH, offsetV, |
&baseAddr, &rowBytes); |
p->dstPixMap.baseAddr = baseAddr; |
p->dstPixMap.rowBytes = rowBytes; |
p->capabilities->flags = codecImageBufferIsOnScreen; |
bail: |
return err; |
} |
pascal ComponentResult |
ImageCodecDisposeMemory(Handle storage, Ptr data) |
{ |
return noErr; |
} |
Some video hardware boards that use an overlay plane require that the image area on screen be flooded with a particular RGB value or alpha-channel in order to have the overlay buffer “show through” at that location. Codecs that require this support should set the screenFloodMethod
and screenFloodValue
fields of the CodecDecompressParams
record during ImageCodecPreDecompress
. The ICM then manages the flooding of the screen buffer. This method is more reliable than having the codec attempt to flood the screen itself, and will ensure compatibility with future versions of QuickTime.
QuickTime functions support packetizing compressed data streams, primarily for video conferencing applications. For this purpose, the field preferredPacketSizeInBytes
was added to the compression parameters structure. Codec developers need only use this field.
Packet information is appended, word-aligned, to the end of video data. It is a variable-length array of 4-byte integers, each representing the offset in bits of the end of a packet, followed by another integer containing the number of packet hints, and finally a four-byte identifier indicating the type of appended data:
[boundary #1][boundary #2]...[boundary #N][N]['pkts'] |
Packets are given in bits, because some types of compressed image data (such as H.261) are cut up on bit-boundaries rather than byte-boundaries.
// given: image data, length, and a packet number |
// returns: a pointer to the start of the packet and a packet size, plus |
// information about leading and trailing bits |
char* GetNextPacket(char* data, int len, int packet, long* packet_size, |
char* leading_bits, char* trailing_bits) |
{ |
long *lp, packets; |
lp = (long*) data; // 'data' must be word-aligned |
lp += len/4 - 1; |
if (*lp != 'pkts') |
return nil; |
packets = *lp[ -1 ]; // negative indexing is good for you |
if (packet >= packets) |
return nil; // out of bounds |
lp -= packets; // now 0-indexing into the packet array will work |
if (packet == 0) |
{ |
*packet_size = (lp[0] + 7)/8; // count the bits |
*leading_bits = 0; |
*trailing_bits = lp[0] % 8; |
return data; // in case of 0-length packet |
} |
else |
{ |
*packet_size = ( lp[pktnum] - lp[pktnum-1] + 7) / 8; |
*leading_bits = lp[packet-1] % 8 ? 8 - lp[packet-1] % 8 : 0; |
*trailing_bits = lp[packet] % 8; |
return data + lp[packet-1] / 8; |
} |
} |
Note that this technique can be used for further extensions by the addition of further appended formats. The last two words are always the number of words and an extension identifier.
The DV image compressor component makes it possible to compress QuickTime video data into DV format. It is invoked automatically by the Image Compression Manager when an application requests output of type kDVCNTSCCodecType
for NTSC DV data or kDVCPALCodecType
for PAL DV data.
When creating NTSC video, the DV image compressor component generates 720 X 480 frames. When creating PAL video, it generates 720 X 576 frames.
Note: Many DV devices use IEEE 1394 (FireWire) serial connections for input/output operations. QuickTime supports compression and decompression of DV data, but it does not include support for FireWire communication. You need additional software to communicate with DV devices.
The DV image decompressor component makes it possible to decompress DV video data. It is invoked automatically by the Image Compression Manager when an application specifies input of type kDVCNTSCCodecType
for NTSC DV data or kDVCPALCodecType
for PAL DV data.
There are two quality modes for DV decompression:
In the low-quality mode, which is the default, the DV image decompressor component generates a 1/4-screen image. When operating in this mode, the component uses approximately 25% of the video data in the DV stream and correspondingly fewer system resources.
In the high-quality mode, the component processes all of the video data in the DV stream. Applications can specify the high-quality modem by calling the SetMediaPlayHints
function with the hintsHighQuality
flag set.
When a computer includes a video display adapter that performs YUV decompression in hardware, the DV image decompressor can use a YUV decompressor component written to use the hardware decompression capabilities in place of the software YUV decompressor in QuickTime, resulting in even higher performance.
You can specify the size of the image buffer used by your image compressor or decompressor component. When your component calls the ImageCodecPreDecompress
or ImageCodecPreCompress
function, you can specify the size of the buffer as follows:
In the CodecDecompressParams
or CodecCompressParams
record, set the codecWantsSpecialScaling
flag in the flags
field of the CodecCapabilities
record.
Provide values for the requestedBufferWidth
and requestedBufferHeight
fields in the CodecDecompressParams
or CodecCompressParams
record.
This is illustrated in Listing 9-5.
Listing 9-5 Specifying the size of an image buffer for a codec
p->capabilities->flags |= codecWantsSpecialScaling; |
p->requestedBufferWidth = 720; |
p->requestedBufferHeight = 480; |
© 2005, 2006 Apple Computer, Inc. All Rights Reserved. (Last updated: 2006-01-10)