Chapter 24. Getting Started with the Compression Library

This chapter describes how to use the Compression Library API to compress and decompress image, audio, and video data. The Compression Library API has three basic interfaces:

In addition, the Compression Library supports Cosmo Compress, an optional hardware JPEG video codec, which connects to the Galileo family of video devices to allow real-time JPEG video capture and playback.

In this chapter:

Overview of the Compression Library API

This section describes how each type of interface is used and provides error handling information.

Still Image API

The single image method is designed to make still image compression as simple as possible. It is the simplest, yet most limited of the three. It consists of two calls, one for compression and one for decompression. No interframe compression/decompression, such as the method that takes advantage of similarities between frames in MPEG, is possible with this interface.

Sequential Access API

The sequential interface is designed for audio/video-streaming applications where the input is live, or where there is no control over playback and when the amount of compressed data for each frame is known in advance; in fact, an error is reported if insufficient data is passed. This interface is more complex, requiring a series of compress or decompress calls to be encapsulated within an open-close block. Each compressor or decompressor keeps state information appropriate to the selected compression algorithm in parameters that you can query and set.

Buffered Access API

The buffered interface is designed for:

  • VCR-like control over the audio/video stream

  • maximum efficiency by buffering compressed data and uncompressed frames

  • blocking and nonblocking access

  • transparent buffering for hardware acceleration or for multiprocessor operation

  • multithreaded applications

This interface includes the calls of the sequential interface, plus buffer-management routines to access the compressed data and the uncompressed frame buffers.

The buffer management routines allow blocking and nonblocking access and accumulation of compressed data and decompressed frames. The compression or decompression modules can each be placed in separate processes. Separating the processes allows the compression or decompression process to get ahead a few frames, which is advantageous for algorithms such as MPEG, which compress the data using techniques that take advantage of similarities between frames, and it also facilitates hardware acceleration.

About File I/O and Error Handling

In the CL, file I/O is handled by the caller. The CL has an error handler that prints error messages to stderr. Most CL routines return a negative error code upon failure.

You can override the default error handling routine and establish an alternate compression error handling routine using clSetErrorHandler().

The function prototype for clSetErrorHandler() is:

CL_ErrFunc clSetErrorHandler(CL_ErrFunc efunc)

where:

efunc 

is a pointer to an error handling routine declared as:

void ErrorFunc(CLhandle handle, int code, const char* fmt,...)

The returned value is a pointer to the previous error handling routine.

The code fragment in Example 24-1 demonstrates how to silence error reporting for a section of code.

Example 24-1. Using a Custom Error Handling Routine

#include <cl.h>
...
CL_ErrFunc originalErrorHandler;
void SilentCLError(CLhandle handle, int errorCode,
const char* fmt, ...)
{
/* ignore all CL errors */
}

...
originalErrorHandler = clSetErrorHandler(silentCLError);
/* cl errors here will go unnoticed */

...
clSetErrorHandler(originalErrorHandler);
/* back to normal reporting of CL errors */
...


Using the Still Image Interface

A simple interface exists for compressing or decompressing still images with a single call. To compress a still image, use clCompressImage(), which compresses the data from the specified frameBuffer, stores the compressed image in compressedData, and stores its resulting size in compressedBufferSize.

Pass to clCompressImage() the compression scheme; the width, height, and format of the image; the desired compression ratio; pointers to reference the buffer containing the image and the buffer that is to store the compressed data; and a pointer to return the size of the compressed data.

You should allocate a buffer large enough to store the compressed data. In most cases, a buffer the size of the source image plus the maximum header size, which you can get by calling clQueryMaxHeaderSize(), is sufficient. When calculating the data storage of the source image, you can use the CL macro CL_BytesPerPixel() to determine the number of bytes per pixel for certain packing formats.

The function prototypes for the compress and decompress image routines are:

int clCompressImage(int compressionScheme, int width,
int height, int originalFormat, float compressionRatio,
void *frameBuffer, int *compressedBufferSizePtr,
void *compressedData)
int clDecompressImage(int decompressionScheme, int width,
int height, int originalFormat,int compressedBufferSize,
void *compressedData, void *frameBuffer)

where:

compressionScheme 


is the compression or decompression scheme to use.

width 

is the width of the image.

height 

is the height of the image.

originalFormat 

is the format of the original image to (de)compress. For video, use CL_RGB, CL_RGBX, CL_RGBA, CL_RGB332, CL_GRAYSCALE, CL_YUV, CL_YUV422, or CL_YUV422DC. For audio, use CL_MONO or CL_STEREO_INTERLEAVED.

compressionRatio 


is the target compression ratio. The resulting quality depends on the value of this parameter and on the algorithm that is used. Use 0.0 to specify a nominal value. The nominal values for some of the algorithms are:

MVC1

5.3:1

JPEG

15.0:1

MPEG

48.0:1


frameBuffer  

is a pointer to the frame buffer that contains the uncompressed image data.

compressedBufferSizePtr 


is a pointer to the size, in bytes, of the compressed data buffer. If it is specified as a nonzero value, the size indicates the maximum size of the compressed data buffer. The value pointed to is overwritten by clCompressImage() when it returns the actual size of the compressed data.

compressedBufferSize 


is the size of the compressed data in bytes.

compressedBuffer 


is a pointer to the compressed data buffer.

Use clDecompressImage() to decompress an image. clDecompressImage() decompresses the data that is stored in compressedBuffer, whose size is compressedBufferSize, and stores the resulting image in frameBuffer.

The values of the state parameters that are used in conjunction with the other compression library calls have no effect on these routines, but their defaults do. The arguments width, height, originalFormat, and compressionRatio function the same as the state parameters by the same names but are given as direct arguments to facilitate the single-command interface.

Example 24-2 demonstrates how to compress and decompress a color image using the JPEG algorithm. The image is 320 pixels wide by 240 pixels high and its data is in the RGBX format.

Example 24-2. Compressing and Decompressing a Single Frame

/* Compress and decompress a 320 by 240 RGBX image with JPEG */
int frameIndex, compressedBufferSize, maxCompressedBufferSize;
int *compressedBuffer, frameBuffer[320][240];

/* malloc a big enough buffer */
maxCompressedBufferSize = 320 * 240 * CL_BytesPerPixel(CL_RGBX)
                                + clQueryMaxHeaderSize(CL_JPEG);
compressedBuffer = (int *)malloc(maxCompressedBufferSize);

/* Compress and decompress it */
clCompressImage(CL_JPEG, 320, 240, CL_RGBX, 15.0,
    frameBuffer, &compressedBufferSize, compressedBuffer);
clDecompressImage(CL_JPEG, 320, 240, CL_RGBX,
    compressedBufferSize, compressedBuffer, frameBuffer);


Using the Sequential Frame Interface

This section describes how to work with sequential frames of audio or video data. See “Using the Buffering Interface” for a description of how to work with nonsequential data, or for situations where the decompression rate is different from the compression rate.

Compressing a Sequence of Frames

To compress sequential data and audio/video streams, use a compressor. A compressor is an abstraction that modularizes compression operations.

To compress a sequence of frames:

  1. Open a compressor to establish the beginning of a sequence of compression calls.

  2. Compress frames one at a time, storing the compressed data after each frame has been compressed.

  3. Close the compressor to deallocate the resources associated with that compressor.

Each of these steps is discussed in detail in the following sections.

Opening a Compressor

Call clOpenCompressor() to open a compressor for a given algorithm. Its function prototype is:

int clOpenCompressor(int scheme, CLhandle *handlePtr)

where:

scheme 

is the compression scheme to use.

handlePtr 

is a pointer, which is overwritten by the returned handle of the compressor that is used by subsequent calls to identify the compressor.

More than one compressor can be open at a time. Use the handle that is returned in handle to identify a specific compressor.

Compressing Frames

After a compressor has been opened, call clCompress() to compress the data. Pass to clCompress() the handle returned by clOpenCompressor(), the number of frames to be compressed, and pointers to reference the frame buffer containing the data frames, the size of the data, and the location of the buffer that is to store the compressed data.

The function prototype for clCompress() is:

int clCompress(CLhandle handle, int numberOfFrames,
void *frameBuffer, int *compressedDataSize,
void *compressedBuffer);

where:

handle  

is a handle to the compressor

numberOfFrames 


is the number of frames to compress: generally 1 for video data, an appropriate block size for audio data, or either CL_CONTINUOUS_BLOCK or CL_CONTINUOUS_NONBLOCK in order to continue compression until either the frame buffer is marked done or clCloseCompressor() is called. With CL_CONTINUOUS_NONBLOCK, the call to clCompress() returns immediately while the compression occurs in a separate thread; CL_CONTINUOUS_BLOCK blocks until compression is completed.

frameBuffer  

is a pointer to the location of the buffer that contains the data that is to be compressed. Using a NULL argument here invokes the buffered interface that is described in “Using the Buffering Interface”. An error is reported if no buffer exists. Some compressors allow a value of CL_EXTERNAL_DEVICE, indicating a direct connection to an external audio or video source.

compressedDataSize 


is a pointer to the returned size of the compressed data in bytes.

compressedBuffer 


is a pointer to the location where the compressed data is to be written. Using a NULL argument here invokes the buffered interface that is described in “Using the Buffering Interface”.

Call clCompress() once to compress numberOfFrames sequential frames. clCompress() reads the raw data from the location pointed to by frameBuffer and writes the compressed data to the location pointed to by compressedBuffer. clCompress() returns either the number of frames successfully compressed, or in the case of CL_CONTINUOUS_NONBLOCK, returns SUCCESS immediately.

The size of the compressed data is stored in compressedDataSize, even if this size exceeds the COMPRESSED_BUFFER_SIZE state parameter. If COMPRESSED_BUFFER_SIZE is less than the actual size returned by clCompress(), then the data returned in compressedBuffer is not complete.

An application-allocated compressed buffer must be at least COMPRESSED_BUFFER_SIZE bytes. This parameter should be determined by calling clGetParams() after the frame buffer dimensions are defined by clSetParams(). It is not required to set the COMPRESSED_BUFFER_SIZE, because the default is the largest possible compressed data size, which is computed from the given parameters.

Closing a Compressor

To close a compressor, call clCloseCompressor() with the handle of the compressor you wish to close. This frees resources associated with the compressor.

The code fragment in Example 24-3 demonstrates how to compress a series of frames using the CL_MVC1 algorithm. A compressor is opened, then a compression loop is entered, where frames are accessed one at a time and compressed using the selected algorithm, then written to a data buffer. The compressor is closed when all of the frames have been compressed.

Example 24-3. Compressing a Series of Frames

#include <dmedia/cl.h>

int pbuf[][2] = {
    CL_IMAGE_WIDTH,  0,
    CL_IMAGE_HEIGHT, 0,
    CL_COMPRESSED_BUFFER_SIZE, 0
};
 ...
/* Compress a series of frames */
clOpenCompressor(CL_MVC1, &handle);

/* set parameters */
pbuf[0][1] = 320;
pbuf[1][1] = 240;
clSetParams(handle, (int *)pbuf, 4);
/* allocate the required size buffer */
clGetParams(handle, (int *)pbuf, 6);
compressedBuffer = malloc(pbuf[2][1]);

for(i = 0; i < numberOfFrames; i++)
{
    /* Get a frame from somewhere */
    ...
    clCompress(handle, 1, frameBuffer, &compressedBufferSize,
        compressedBuffer);
    /* Write the compressed data to somewhere else. */
    ...
}
clCloseCompressor(handle);


Decompressing a Sequence of Frames

Decompressing sequential data and audio/video streams requires the use of a decompressor. A decompressor is an abstraction that modularizes decompression operations.

To decompress a sequence of frames:

  1. Query the stream header to get the compression scheme used.

  2. Open a decompressor to establish the beginning of a sequence of decompression calls.

  3. Decompress frames one at a time, storing the decompressed data after each frame has been decompressed.

  4. Close the decompressor to deallocate the resources associated with that decompressor.

Each of these steps is discussed in detail in the following sections.

Getting Stream Information

To determine which scheme to pass to the decompressor, use clQueryScheme() to get the scheme from the 16 bytes of the stream header (see Table 24-1 for a list of typical header contents, and Table 24-2 for a list of additional video stream header contents). clQueryScheme() returns the scheme, or the (negative) error code when an error occurs.

Once you determine the scheme, you can open the decompressor and read the header using clReadHeader(), which returns the actual size of the header, or zero if none is detected. Use clQueryMaxHeaderSize(), which returns the maximum size of the header, or zero if none is detected, to determine the size of the header to send to clReadHeader(). You should free the space used for the header buffer when you are finished with it.

clReadHeader() is generally called before clCreateBuf() to help calculate the compressed buffer size. It uses the data passed to it without affecting the buffering. clReadHeader() also sets up any state parameters that can be determined from the header.

The function prototypes are:

int clQueryScheme(void *header)
int clQueryMaxHeaderSize(int scheme)
int clReadHeader(CLhandle handle, int headerSize,void *header)

where:

header 

is a pointer to a buffer containing at least 16 bytes of the header.

scheme 

is the decompression scheme to use.

handle 

is a handle to the decompressor.

headerSize 

is the maximum size of the header in bytes.

header 

is a pointer to a buffer containing the header.

A typical header begins with a start code and a size, followed by parameter-value pairs such as those listed in Table 24-1.

Table 24-1. Typical Stream Header Contents

Parameter

Information supplied

CL_ALGORITHM_ID

Algorithm scheme

CL_ALGORITHM_VERSION

Version of the algorithm

CL_INTERNAL_FORMAT

Format of images immediately before compression

CL_NUMBER_OF_FRAMES

Number of frames in the sequence

CL_FRAME_RATE

Frame rate

In addition, video algorithms usually supply the width and height parameters listed in the header, as shown in Table 24-2.

Table 24-2. Additional Video Stream Header Contents

Parameter

Information Supplied

CL_IMAGE_WIDTH

Width

CL_IMAGE_HEIGHT

Height

The code fragment in Example 24-4 demonstrates how to query a stream header and read its contents.

Example 24-4. Getting the Decompression Scheme from a Header

#include <cl.h>
...
int decompressionScheme;
...
/*
 * Determine the scheme from the first 16 bytes of the
 *  header(from the beginning of video data)
*/
header = malloc(16);
read(inFile, header, 16);
decompressionScheme = clQueryScheme(header);
if(decompressionScheme < 0) {
fprintf(stderr, “Unknown compression scheme in stream
header.0);
exit(0);
}
free(header);

clOpenDecompressor(decompressionScheme, &decompressorHdl);

/* Find out how big the header can be. */
headerSize = clQueryMaxHeaderSize(decompressionScheme);
if(headerSize > 0) {
/* Read the header from the beginning of video data */
header = malloc(headerSize);
lseek(inFile, 0, SEEK_SET);


Opening a Decompressor

Call clOpenDecompressor(), with the desired compression scheme and a pointer for returning a handle, to open a decompressor for a given algorithm. Its function prototype is:

int clOpenDecompressor(int scheme, CLhandle *handlePtr)

where:

scheme 

is the decompression scheme to use

handlePtr 

is a pointer to the returned handle of the decompressor that is used by subsequent calls to identify the decompressor.

More than one decompressor can be open at a time. Use the handle that is returned in handle to identify a specific decompressor.

Decompressing Frames

After a decompressor has been opened, call clDecompress() to decompress the data. Pass to clDecompress() the handle returned by clOpenDecompressor(), the number of frames to be decompressed, the size of the data, and pointers to reference the decompressed data and the frame buffer that contains the compressed frames.

The function prototype for clDecompress() is:

int clDecompress ( CLhandle handle, int numberOfFrames,
                   int compressedDataSize, void *compressedData
                   void *frameBuffer);

where:

handle 

is a handle to the decompressor.

numberOfFrames 


is the number of frames to decompress: generally 1 for video data, an appropriate block size for audio data, or either CL_CONTINUOUS_BLOCK or CL_CONTINUOUS_NONBLOCK in order to continue decompression until either the frame buffer is marked done or clCloseDecompressor() is called. With CL_CONTINUOUS_NONBLOCK, the call to clDeCompress() returns immediately while the compression occurs in a separate thread; CL_CONTINUOUS_BLOCK blocks until compression is completed. Using a NULL argument invokes the buffered interface that is described in “Using the Buffering Interface”.

compressedDataSize 


is a pointer to the returned size of the decompressed data in bytes.

compressedData 


is a pointer to the location where the decompressed data is to be written.

frameBuffer  

is a pointer to the location of the frame buffer that contains the data that is to be decompressed. Some compressors allow a value of CL_EXTERNAL_DEVICE, indicating a direct connection to an external audio or video source. Using a NULL argument invokes the buffered interface that is described in “Using the Buffering Interface”. An error is reported if no buffer exists.

Closing a Decompressor

To close a decompressor, call clCloseDecompressor() with the handle of the decompressor you wish to close.

The code fragment in Example 24-5 demonstrates how to decompress a series of 320 × 240 (32-bit) RGBX frames by using the CL_MVC1 algorithm. A decompressor is opened, then a decompression loop is entered, where frames are accessed one at a time and decompressed by using the selected algorithm, then written to a location such as the screen. The decompressor is closed when all of the frames have been compressed.

Example 24-5. Decompressing a Series of Frames

#include <cl.h>
...
int compressedBufferSize;
int compressedBuffer[320][240], frameBuffer[320][240];
int     width, height, k;
static int    paramBuf[][2] = {
    CL_IMAGE_WIDTH, 0,
    CL_IMAGE_HEIGHT, 0,
    CL_ORIGINAL_FORMAT, 0,
};
width = 320;
height = 240;

clOpenDecompressor(CL_MVC1, &decompressorHdl);
paramBuf[0][1] = width;
paramBuf[1][1] = height;
paramBuf[2][1] = CL_RGBX;
clSetParams(decompressorHdl, (int *)paramBuf,
sizeof(paramBuf) / sizeof(int));

for (k = 0; k < numberOfFrames; k++)
{ /* Decompress each frame and display it */
  dataSize = GetCompressedVideo(k, frameSize, data);
  clDecompress(decompressorHdl, 1, dataSize, data,
frameBuffer);
  lrectwrite(0, 0, width-1, height-1,
(unsigned int *)frameBuffer);
}
/* Close Decompressor */
clCloseDecompressor(decompressorHdl);


Using the Buffering Interface

Buffers are used to manage compression and decompression for data that is accessed randomly, or when it is necessary to separate the task into several processes or across multiple processors. Buffering allows the accumulation of compressed data to be independent of that of decompressed frames. The buffering interface can be used for multithreaded applications.

Buffers are implemented as ring buffers in libcl. A ring buffer contains a number of blocks of arbitrary size. It maintains a pointer to the buffer location, a size, and pointers to the Head of newest and Tail of oldest valid data. Separate processes can be producing (adding to the buffer) and consuming (removing from the buffer).

Figure 24-1 shows a conceptual drawing of a ring buffer.

Figure 24-1. Ring Buffer


The circle represents the ring buffer. The shaded part of the circle contains frames or data, depending on the buffer type; the blank part is free space. The size of the data (or the number of frames) available and the size of the space (or the number of frames of space) are shown by the arrows within the circles. Head marks the location where new data or frames, depending on the buffer type, are inserted. Tail marks the location where the oldest data or frames, depending on the buffer type, are removed. The head and tail march around the circle as data or frames, depending on the buffer type, are produced and consumed. The double vertical bar at the top signifies the discontinuity between the end of the buffer and the beginning of the buffer in linear physical memory.

Creating a Buffer

The buffer management routines allow buffer space to be allocated by the library (internal) or by the application (external). A buffer often already exists in memory where the frames exist (on compression) or need to be placed (on decompression). External buffering allows this to happen without having to copy the data to or from an internal buffer. An external buffer is managed entirely within libcl as a ring buffer.

Use clCreateBuf() to create an internal or external buffer. Use clDestroyBuf() to destroy an internal or external buffer. If clDecompress() or clCompress() is called with NULL for the compressed data or frame buffer parameters, then the buffer specified by clCreateBuf() is used. An error is reported if no buffer was created.

The function prototypes are:

CLbufferHdl * clCreateBuf (CLhandle handle, int bufferType,
                 int blocks, int blockSize, void **bufferPtr)
int          clDestroyBuf (CLbufferHdl bufferHdl)

where:

handle 

is the handle to the compressor or decompressor

bufferType 

specifies the type of the ring buffer, which can be either:

  • CL_FRAME for a frame buffer

  • CL_DATA for a data buffer

blocks 

specifies the number of blocks in the buffer

blockSize 

specifies the size in bytes of the block. This value is either 1 for data buffering or a multiple of the frame size for frame buffering

bufferPtr 

is a pointer to a pointer to the ring buffer. If it points to a NULL pointer, it specifies an internally allocated buffer, and the value it points to receives the buffer pointer

bufferHdl 

is a handle to the buffer

The handle returned in bufferHdl is used in subsequent buffering calls, with which you can get the buffer handle, and get the compressor or decompressor handle.

Use clQueryBufferHdl() to get the buffer handle from a compressor or decompressor handle. Its function prototype is:

CLbufferHdl clQueryBufferHdl(CLhandle handle,
                           int bufferType, void **bufferPtr2)

Use clQueryHandle() to get the compressor or decompressor handle from a buffer handle. Its function prototype is:

CLhandle clQueryHandle(CLbufferHdl bufferHdl)

The code fragment in Example 24-6 demonstrates how to create and use an internal buffer.

Example 24-6. Creating and Using an Internal Buffer

#include <cl.h>
CLhandle       handle;
CLbufferHdl    bufferHdl;
void    *buffer;
 ...
clOpenCompressor(CL_MVC1, &handle);

/* Create a buffer of 10 blocks of size 10000 */
buffer = NULL;
bufferHdl = clCreateBuf(handle, CL_DATA, 10, 10000, &buffer);
bufferHdl = clQueryBufferHdl(handle, CL_DATA, &buffer);
handle = clQueryHandle(bufferHdl);
 ...
clDestroyBuf(bufferHdl);
clCloseCompressor(handle);

The code fragment in Example 24-7 demonstrates how to create and use an external buffer.

Example 24-7. Creating and Using an External Buffer

#include <cl.h>
CLhandle       handle;
CLbufferHdl    bufferHdl;
void    *buffer;
clOpenCompressor(CL_MVC1, &handle);

/* Create a buffer of 10 blocks of size 10000 */
buffer = malloc(10*10000);
bufferHdl = clCreateBuf(handle, CL_DATA, 10, 10000, &buffer);
bufferHdl = clQueryBufferHdl(handle, CL_DATA, &buffer);
handle = clQueryHandle(bufferHdl);
 ...
clDestroyBuf(bufferHdl);
clCloseCompressor(handle);


Managing Buffers

The buffer management routines are used for both uncompressed (or decompressed) frames and compressed data. When used for compressed data, they return the number of blocks (of selectable byte size) of valid contiguous data (or free space for data). When used for frames, they return the actual number of valid contiguous frames (or free space for frames).

Use clQueryFree() to find out how much free space is available and where it is located.

Use clUpdateHead() to notify the library that data has been placed in the ring buffer and to update the head pointer.

Use clQueryValid() to find out how many blocks of valid data are available and where they are located.

Use clUpdateTail() to notify the library that valid data has been consumed from the ring buffer and that data is no longer needed.

Use clDoneUpdatingHead() to notify a decompressor that no more data will be arriving, in which case clDecompress() returns when the buffer empties.

The function prototypes are:

int clQueryFree (CLbufferHdl bufferHdl, int space
                void **freeData, int *wrap)
int clUpdateHead (CLbufferHdl bufferHdl, int amountToAdd);
int clQueryValid (CLbufferHdl bufferHdl, int amount,
                 void **ValidData, int *wrap)
int clUpdateTail (CLbufferHdl bufferHdl, int amountToRelease)
int clDoneUpdatingHead (CLbufferHdl bufferHdl)

where:

bufferHdl 

is a handle to a compressor buffer.

space 

is the number of blocks of free space in the frame buffer to wait for. If it is zero, then the current number of blocks of space is returned without waiting.

freeData 

is a pointer to the returned pointer to the location where data or frames can be placed.

wrap 

is the number of blocks that have wrapped to the beginning of the ring buffer (see Figure 24-2 and the accompanying discussion). If it is greater than zero, then the end of the ring buffer has been reached and the routine return value will not increase (on subsequent calls) until either clUpdateHead() for free space or clUpdateTail() for valid data has been called.

amountToAdd 

is the number of blocks of free space that were written by the caller and are ready to be consumed by the library.

amount 

is the number of blocks of valid data in the data buffer to wait for. If it is zero, then the number of blocks currently available is returned without waiting.

validData 

is a pointer to the returned pointer to the location where valid data can be retrieved.

amountToRelease 


is the number of blocks of valid data that were consumed by the call and can be reused by the library.

Each compressor or decompressor can have a (compressed) data buffer and a (uncompressed) frame buffer.

The block size for the uncompressed frame buffer must be a multiple of the size of one frame. This value, multiplied by the number of blocks specified, determines how many frames ahead a decompressor can get if you allow it to work ahead.

Producing and Consuming Data in Buffers

Figure 24-2 shows snapshots of the buffer state over time as a sequence of produce and consume processes operate on the buffer. Initially, the buffer is empty and both Head and Tail point to the beginning of the buffer. When Head and Tail are equal, the buffer is either empty or full—in this case, the buffer is empty. The library keeps track of whether the buffer is empty or full internally.

In the first frame of Figure 24-2, a process begins producing—adding data to the buffer. First, a call is made to clQueryFree() to determine how much free space is available. An amount equal to the entire buffer size is returned. Data is written to the buffer, then the location of Head is updated to point to the beginning of the next available free space.

In the second frame of Figure 24-2, the next call to clQueryFree() returns the free space that exists from Head to Tail. More data is written and the Head is updated once again.

In the third frame of Figure 24-2, a process begins consuming—taking data from the buffer. A call is made to clQueryValid() to determine the amount of valid data in existence. The size of the data that was written by the producers so far is returned. Data is read from the beginning of the buffer to the desired location, and Tail is updated to point to the next location containing valid data.

The final frame of Figure 24-2 shows what happens when the free space is not contiguous. When the next producer queries for the available free space, two pieces of free space exist—one on each side of the buffer discontinuity. The first piece of free space, which is from Head to the end of the buffer, is returned as usual. The second piece of free space, which is from the beginning of the buffer to Tail, is returned in the wrap argument. You can't write data across the buffer boundary, so it must be written to the buffer in two steps. First write the data until the end of the buffer is reached, then write the data from the beginning of the buffer until all of the data has been used. Head can then be updated to point to the next available free space.

The process for reading data across the frame discontinuity is analogous.

Figure 24-2. Snapshots of Buffer State During Producing and Consuming Processes


Figure 24-3 shows the architecture of the buffer management. Rectangles represent code modules that can be placed in separate synchronized processes. The buffer management routines are shown within the boxes. Arrows show the flow of data from the modules to and from the buffers

Figure 24-3. Flow of Data in a Buffered Compression and Decompression Scheme


Creating a Buffered Record and Play Application

This section provides several examples of how to use buffering. Blocking and nonblocking playback and record examples are provided.

Creating a Basic Buffered Playback Application

The code fragment in Example 24-8 demonstrates how to use buffers for a playback application. The amount of space is queried, the data is read directly into the data buffer, and the decompressor is notified of the change. The data can then be decompressed and retrieved by querying the number of frames, displaying them directly from the frame buffer, then releasing the consumed frames.

Example 24-8. Using Buffers for Playback

#include <cl.h>
 ...
actualLen = clQueryFree(decompressorHdl, len, &buf, &wrap);
read(fd, buf, actualLen);
len = clUpdateHead(dataHdl, actualLen);

clDecompress(decompressorHdl, 1, 0, NULL, NULL);

actualNumberOfFrames = clQueryValid(frameHdl, numberOfFrames,
  &frameBuffer, &wrap);
ConsumeFrames(actualNumberOfFrames, frameBuffer);
numberOfFrames = clUpdateTail(bufferHdl, actualNumberOfFrames);

clUpdateHead() indicates to the library that the data has been placed in the data buffer, but does not copy the data.

clDecompress() reads compressed data from the data buffer and writes uncompressed frames to the frame buffer. If space for a frame exists in the frame buffer, then it begins decompressing directly to the frame buffer. It consumes data from the data buffer until there is no more data, then it sleeps for a while and periodically continues to check for data until there is enough data. When it finishes decompressing a frame, it updates the frame buffer pointers and returns. clDecompress() does not return until decompression is complete or until an error occurs.

If no more data will be added to the buffer, the application can call clDoneUpdatingHead() so that the library will not stall.

clQueryValid() returns the pointer into the frame ring buffer. clUpdateTail() is required to free the internal frame buffer space, which you don't want to happen until after you consume it. The pointer to the next valid frame is kept internally, and only the actual number of frame buffers that have been decompressed are returned.

The size (or numberOfFrames) returned by the routines are for the contiguous data (or frames, depending on the buffer type). The wrap argument of the clQuery() routines returns the actualLen (or numberOfFrames) that have wrapped to the beginning of the buffer.

The frame accesses will not cross the buffer boundary, and the wrap argument does not need to be used if both:

  • the allocated size of the frame ring buffer is a multiple of the size of a frame times the numberOfFrames that will be requested

  • the same number of frames will always be requested

If the len (or numberOfFrames) passed to the clQuery() routines is greater than zero, the routine blocks until that much data (or that many frames) is available. If it is less than or equal to zero, then the routine returns immediately with whatever data is available. In either case, the buffer pointers are not adjusted until the clUpdate() routines are called.

Creating a Nonblocking Buffered Playback Application

The code fragment in Example 24-9 demonstrates how to implement nonblocking playback.

Example 24-9. Using Buffers for Nonblocking Playback

actualLen = clQueryFree(decompressorHdl, 0, &buf, &wrap);
if((actualLen > MIN_READ_SIZE) || (wrap > 0)) {
   read(fd, buf, actualLen);
   len = clUpdateHead(decompressorHdl, actualLen);
}
/* Go do something else */
 ...

Each call to clQueryFree() returns the same buf pointer, but increasing values of actualLen until MIN_READ_SIZE is reached, whereupon clUpdateHead(dataHdl) updates the pointers and the next call to clQueryFree() returns a different buf pointer and a reset actualLen. If wrap becomes greater than zero, the end of the buffer has been reached and actualLen will not get any larger, so the amount remaining in the buffer must be consumed.

Creating a Buffered Record Application

The code fragment in Example 24-10 demonstrates how to use buffers for recording.

Example 24-10. Using Buffers for Recording

actualNumberOfFrames = clQueryFree(bufferHdl, numberOfFrames,
                                   &frameBuffer, &wrap);
ProduceFrames(actualNumberOfFrames, frameBuffer);
numberOfFrames = clUpdateHead(bufferHdl, actualNumberOfFrames);

clCompress(compressorHdl, 1, NULL, 0, NULL);

actualBufSize = clQueryValid(compressorHdl, bufSize, &buf,
                             &wrap);
write(fd, buf, actualBufSize);
bufSize = clUpdateTail(compressorHdl, actualBufSize);

The amount of free space is queried, the frames are read directly into the frame buffer, and the compressor is notified of the change. The frames can then be compressed and the data can be retrieved by querying the amount of the data, consuming directly from the data buffer, then releasing the consumed data.

clUpdateHead() indicates that the frames have been placed in the frame buffer, but does not copy the data.

clCompress() reads from the frame buffer and writes to the data buffer. If a frame exists in the frame buffer, then it begins compressing directly from the frame buffer. It places compressed data in the data buffer until there is no more room, then it blocks until there is enough room. When it completes compression of a frame, it updates the frame buffer pointers and returns. clCompress() does not return until compression is complete (or an error occurs).

clQueryValid() returns the pointer into the data ring buffer. clUpdateTail() is required to free the internal data buffer space, which you don't want to happen until after you consume it—in this case, by writing it. The pointer to valid data is kept internally, and clUpdateTail() returns only the actual number of bytes released.

The amount/numberOfFrames returned by the routines are for contiguous data/ frames. The wrap parameter of the clQuery() routines returns the amount/numberOfFrames that have wrapped to the beginning of the buffer.

If the allocated size of the frame ring buffer is a multiple of the size of a frame times the numberOfFrames that will be requested, assuming that the same number of frames is always requested, then the frame accesses will not cross the buffer boundary, and the wrap parameter does not need to be used.

If the amount passed to the clQuery() routines is greater than zero, then the routine blocks until that much data is available. If it is less than or equal to zero, then the routine returns immediately with whatever data is available. In either case, the buffer pointers are not adjusted until the clUpdate() routine is called.

Creating a Nonblocking Buffered Record Application

The code fragment in Example 24-11 demonstrates how to use buffers for nonblocking recording.

Example 24-11. Using Buffers for Nonblocking Recording

actualLen = clQueryValid(dataHdl, 0, &buf, &wrap);
if((actualLen > MIN_READ_SIZE) || (wrap > 0)){
write(fd, buf, actualLen);
len = clUpdateTail(dataHdl, actualLen);
}

Each call to clQueryValid() returns the same buf pointer, but increasing values of actualLen until MIN_READ_SIZE is reached, whereupon clUpdateTail() updates the pointers, and the next call to clQueryValid() returns a different buf pointer and a reset actualLen. If wrap becomes greater than zero, then the end of the buffer has been reached, and actualLen will not get any larger, so the amount remaining in the buffer must be consumed.

Note that the consuming, compressing or decompressing, and producing have been separated into different sets of calls. The most powerful use of the interface is to separate these functional groupings into shared processes using sproc() or to allocate them to separate (shared data) processors. See sproc(2) for more information about using sproc().

The buffers are set up by clCreateBuf(). In order to use data input buffering, clDecompress() receives NULL for compressedData. In order to use frame output buffering, clDecompress() receives NULL for frameBuffer.

clCompress() reads from the frame buffer and writes to the data buffer. If a frame exists in the frame buffer, then it begins compressing directly from the frame buffer. It places compressed data in the data buffer until there is no more room, then it sleeps for a while and checks again until there is enough room. When it finishes compressing a frame, it updates the frame buffer pointers and returns. clCompress() does not return until compression is complete or until an error occurs.

Creating Buffered Multiprocess Record and Play Applications

In the examples in the previous section, consuming, compressing or decompressing, and producing have been separated into different sets of calls. The most powerful use of the buffering interface is to separate these functional groups into shared processes using sproc() or to allocate them to separate (shared data) processors.

The code fragment in Example 24-12 demonstrates how to implement multiprocess playback. The functions in boldface can be implemented as separate processes.

Example 24-12. Using Buffers for Multiprocess Playback

ProduceDataProcess()
  actualLen = clQueryFree(dataHdl, len, &buf, &wrap);
  read(fd, buf, actualLen);
  len = clUpdateHead(dataHdl, actualLen);

DecompressProcess()
  clDecompress(decompressorHdl, 1, 0, NULL, NULL);

ConsumeFrameProcess()
  actualNumberOfFrames = clQueryValid(frameHdl,
    numberOfFrames, &frameBuffer, &wrap);
  lrectwrite(0, 0, width - 1, height - 1, frameBuffer);
  numberOfFrames = clUpdateTail(frameHdl,actualNumberOfFrames);

The code fragment in Example 24-13 demonstrates how to use buffers for multiprocess recording. The functions in boldface can be implemented as separate processes.

Example 24-13. Using Buffers for Multiprocess Recording

ProduceFrameProcess()
  actualNumberOfFrames = clQueryFree(frameHdl,
    numberOfFrames, &frameBuffer, &wrap);
  lrectread(0, 0, width - 1, height - 1, frameBuffer);
  numberOfFrames = clUpdateHead(frameHdl,
    actualNumberOfFrames);

CompressProcess()
  clCompress(compressorHdl, 1, NULL, &compressedDataSize,
      NULL);

ConsumeDataProcess()
  actualBufSize = clQueryValid(dataHdl, bufSize,&buf, &wrap);
  write(fd, buf, actualBufSize);
  bufSize = clUpdateTail(dataHdl, actualBufSize);

This allows the application nonblocking access to compression and decompression. The application will almost always use ProduceDataProcess() for playback and the ProduceFrameProcess() for record, since the single process will block forever within clDecompress()/clCompress() if insufficient data or frames, depending on the buffer type, are supplied. The other processes can be made parts of the main() process. These processes could also be spread across multiple processors.

Programming with the Cosmo Compress JPEG Codec

Cosmo Compress is an optional hardware JPEG accelerator for workstations equipped with Galileo Video options, including: Galileo Video for Indigo2 and Indigo R4000 computers, Indigo2 Video, Indy Video, and Sirius Video. Cosmo Compress is capable of compressing to and decompressing from memory, or directly through a special video connection to Galileo Video options.

Cosmo Compress JPEG is a lossy compression scheme based on psychovisual studies of human perception. Picture information that is generally not noticeable is dropped out, reducing the storage requirement anywhere from 2 to 100 times. Cosmo Compress implements a subset of the JPEG standard especially for video-originated images (baseline JPEG, interleaved YCrCb 8-bit components).

Cosmo Compress Basics

See the Cosmo Compress Execution Environment Release Notes for important prerequisite information and installation instructions. Your workstation must be equipped with the following hardware and software components in order to be able to use Cosmo Compress:

  • Cosmo Compress option and Cosmo Compress software

  • Video option with output capability

  • Iris Development Option software

You can program Cosmo Compress from the Compression Library (CL), using either the buffered or sequential interface along with JPEG-specific and Cosmo-specific CL parameters.

Cosmo Compress has four different modes of operation:

  • compressing video from an external video device into a memory buffer

  • decompressing video from a buffer to an external video device

  • compressing an image stored in memory into another area of memory

  • decompressing a stored compressed image into a buffer

To add Cosmo Compress support to your application:

  1. Include the dmedia/cl_cosmo header in order to get definitions for Cosmo Compress:

    #include <dmedia/cl_cosmo.h>
    

  2. Set Cosmo Compress specific compression parameters:

  3. Specify CL_JPEG_COSMO as the scheme argument for clOpenCompressor() when opening a compressor or clOpenDecompressor() when opening a decompressor. Only one application can have Cosmo Compress open at a time—an error will be returned to the program if another application has Cosmo Compress open.

  4. Compress or decompress frames.

Cosmo Compress Image Formats

This section describes CL image parameters supported by Cosmo Compress. Cosmo Compress works with video fields, 2 of which compose a video frame.

Cosmo Compress requires that input images have height and width dimensions that are multiples of 8 pixels because the JPEG compression algorithm processes images in blocks of 8x8 pixels. The CL associates two sets of image dimensions with an instance of a video compressor or decompressor: one set for uncompressed images (CL_IMAGE_WIDTH and CL_IMAGE_HEIGHT), and one set for compressed images (CL_INTERNAL_IMAGE_WIDTH and CL_INTERNAL_IMAGE_HEIGHT).

For memory-to-memory compression, CL_IMAGE_WIDTH always equals CL_INTERNAL_IMAGE_WIDTH, and CL_IMAGE_HEIGHT always equals CL_INTERNAL_IMAGE_HEIGHT.

Table 24-3 summarizes the image format parameters

Table 24-3. Cosmo Compress Image Format Parameters

Image Attribute

Description

Parameter(s)

Values

Pixel format

Cosmo Compress supports 32-bit RGB only for memory- memory transfers and YCrCb 4:2:2 only for video-memory transfers.

CL_ORIGINAL_FORMAT

CL_RGBX (memory-memory)

CL_YUV (video-memory)

Interlacing

Cosmo Compress operates on interlaced NTSC or PAL video data for video-to- memory compression and memory-to-video decompression. Even and odd fields are compressed as separate images.

DM_IMAGE_INTERLACING

NTSC or CCIR(525):
DM_IMAGE_INTERLACED_EVEN

PAL or CCIR(625):
DM_IMAGE_INTERLACED_ODD

Image orientation

Cosmo Compress compresses/decompresses images that have top-to- bottom orientation. By default, lrectwrite(3g) draws images with bottom-to-top orientation. Use pixmode(3g) to set graphics orientation to PM_TTOB in order to correctly display top-to- bottom images.

CL_ORIENTATION

CL_TOP_DOWN

DM_TOP_TO_BOTTOM (for SGI movies)

Uncompressed image dimensions

Uncompressed image height (in pixels).

CL_IMAGE_HEIGHT

Range: 16–336, in multiples of 8. (NTSC must use either 240 or 248) Default: 248

 

Uncompressed image width (in pixels )

CL_IMAGE_WIDTH

640 (NTSC), 720 (CCIR(525) and CCIR(625)), 768 (PAL). Default: 640

Compressed image dimensions

Compressed image height (in pixels)

CL_INTERNAL_IMAGE_HEIGH T

Range: 16–336, in multiples of 8.

 

Uncompressed image width (in pixels)

CL_INTERNAL_IMAGE_WIDTH

320 (NTSC), 360 (CCIR(525) and CCIR(625)), 384 (PAL). Default: 320


Getting Compressed Image Information

The CL provides a function exclusively for Cosmo Compress that lets you get information such as the size, timestamp, and a relative image index value for images (fields or frames) as they are compressed or decompressed through Cosmo Compress. When compressing from external video, the timestamp returned represents the time at which the first line of the uncompressed field arrived at the Cosmo Compress board.

To get compressed image information:

  1. Call clSetParam() to set the CL_ENABLE_IMAGEINFO parameter to TRUE before compressing or decompressing any frames.

  2. Call clGetNextImageInfo() to get a structure containing information about the compressed image:

    int clGetNextImageInfo(CL_Handle handle,
                      CLimageInfo *info, int sizeofimageinfo)
    

    handle 

    specifies an open handle which is actively compressing or decompressing

    info 

    is a pointer where a CLimageInfo structure is to be placed

    sizeofimageinfo 

    specifies the size of the CLimageInfo structure in bytes

    The CLimageInfo structure is defined in dmedia/cl.h and has the following fields:

    typedef struct {
        unsigned size; /* size of compressed image in bytes */
        unsigned long long ustime;   /* time in nanoseconds */
        unsigned imagecount;       /*  media stream counter */
        unsigned status;   /* additional status information */
    } CLimageInfo;
    

    The ustime field returns a meaningful value only when compressing from or decompressing to an external device. The status field is reserved for future use.


Note: Currently, in order to get valid JPEG data, an application using the CL_JPEG_COSMO compressor must enable clGetNextImageInfo() by setting CL_ENABLE_IMAGEINFO, and then read a CLimageInfo structure corresponding to each compressed image, before calling clCompress to read the compressed image data.

When using the CL_JPEG_COSMO decompressor, you don't need to read CLimageInfo structures. When clGetNextImageInfo() is enabled, the CL uses a small internal buffer to queue the structures during decompression. When this queue fills, the oldest structures are overwritten by new ones.

clGetNextImageInfo() blocks only when it is waiting for the first valid decompressed field to exit the CL_JPEG_COSMO decompressor.

Memory-to-Memory Compression and Decompression

You can use Cosmo Compress to compress images from a memory archive to a buffer. For example, you can use Cosmo Compress to compress images from a movie file to a buffer, and then insert the JPEG-compressed images into a movie file to create a compressed movie. Taking this idea a step further, you can then use Cosmo Compress to scale down the images as it decompresses them, in order to display thumbnail images similar to the ones in Movie Player. See vidmemcomp.c in /usr/people/4Dgifts/examples/dmedia/compression/vidmemcomp for example code that demonstrates these concepts.

Memory-to-Memory Compression

To compress frames into memory using Cosmo Compress:

  1. Set the CL image parameters to characterize the input image data.

  2. Open a CL_JPEG_COSMO compressor.

  3. Compress frames into memory. Each frame contains 2 fields.

When compressing images from memory into a buffer, Cosmo Compress supports image widths of 16–768 and image heights of 16–336, in multiples of 8. You cannot scale images when compressing into memory, therefore, CL_IMAGE_WIDTH equals CL_INTERNAL_IMAGE_WIDTH, and CL_IMAGE_HEIGHT equals CL_INTERNAL_IMAGE_HEIGHT.

The uncompressed data format must be 32-bit RGB (CL_RGBX), and the uncompressed image size cannot be larger than PAL video.


Note: NTSC frames have a width of 243, but Cosmo Compress supports only input image widths that are multiples of 8. For NTSC, you must specify an image width of either 240 (causing the image to be cropped 3 lines from the bottom) or 248 (causing the image to be padded with 5 extra lines). Output image widths do not have to be multiples of 8.

Example 24-14 demonstrates memory-to-memory compression of NTSC video.

Example 24-14. Cosmo Compress Memory-to-Memory Compression

#include <dmedia/cl.h>
...
    int pbuf[][2] = {
        CL_IMAGE_WIDTH,  0,
        CL_IMAGE_HEIGHT, 0,
        CL_COMPRESSED_BUFFER_SIZE, 0
    };
     ...
    /* Compress a series of frames */
    clOpenCompressor(CL_JPEG_COSMO, &handle);

    /* set parameters */
    pbuf[0][1] = 640;
    pbuf[1][1] = 240;
    clSetParams(handle, (int *)pbuf, 4);

    /* allocate the required size buffer */
    clGetParams(handle, (int *)pbuf, 6);
    compressedBuffer = malloc(pbuf[2][1]);

    for(i = 0; i < numberOfFrames; i++)
    {
        /* Get a frame from somewhere */
        ...
        clCompress(handle, 1, frameBuffer,
                   &compressedBufferSize, compressedBuffer);
        /* Write the compressed data to somewhere else. */
        ...
    }
    clCloseCompressor(handle);

After compressing the images, you can use mvInsertCompressedImage() to insert the compressed images into a movie file, as described in “Reading and Inserting Compressed Images” in Chapter 29. Since the JPEG images are stored in fields, you must read two fields for every frame.

Memory-to-Memory Decompression

To decompress JPEG images from memory using Cosmo Compress:

  1. Set the CL image parameters to characterize the output image data.

  2. Open a CL_JPEG_COSMO decompressor.

  3. Decompress frames into a buffer. Each frame contains 2 fields.

You can shrink the images as they are decompressed, which is useful for displaying thumbnail images. When decompressing images from memory into a buffer, Cosmo Compress supports image widths of 16–768 and image heights of 16–336. Scaling can be arbitrary, that is, you can scale the image dimensions down by any amount, and the output image dimensions do not have to be multiples of 8. To shrink images as they are decompressed, make the uncompressed image dimensions (CL_IMAGE_WIDTH and CL_IMAGE_HEIGHT) less than the corresponding compressed image dimensions (CL_INTERNAL_IMAGE_WIDTH and CL_INTERNAL_IMAGE_HEIGHT).

Compressing and Decompressing Video Through External Connections to Cosmo Compress

You can use Cosmo Compress as a real-time JPEG codec between your application and an external video device. This section explains how to use Cosmo Compress to compress images from an external video connection into memory and decompress JPEG images from memory to a video device.

Video-to-Memory Compression

To capture video from an external video device using Cosmo Compress:

  1. Connect the video device to the appropriate port. For example, use either analog port 1 or digital port 1. Video port connections are managed from the videopanel control panel.

  2. Open a connection to the video server by calling vlOpenVideo( "" ).

  3. Create the video transfer paths.

    • Get the source (VL_SRC) node for the video signal connection by calling vlGetNode().

    • Get the drain(VL_DRN) node for the Cosmo Compress connection by calling vlGetNode().

    • Create the path from source to drain by calling vlCreatePath().

    • Set up the path to share (VL_SHARE) data by calling vlSetupPaths().


    Tip: Cosmo Compress is not a video node; it is a separate device. Therefore the VL does not have a Cosmo VLnode for video paths. The port to which Cosmo Compress is connected (for example, the digital video output) is the video drain node.


  4. Set the appropriate video synchronization mode. Use slave mode (VL_EV1_SYNC_SLAVE) when capturing from the analog port; use internal mode (VL_SYNC_INTERNAL) when capturing from the digital port.

  5. Set the CL parameters for image dimensions, quality factor, and compressed image information (CL_ENABLE_IMAGEINFO).

  6. Open a CL_JPEG_COSMO compressor.

  7. Call clGetNextImageInfo() to get a structure containing information about the compressed image.

  8. Start the video transfer.

  9. Use the CL buffered interface to compress frames by calling clCompress() with CL_CONTINUOUS_NONBLOCK as the framecount parameter and CL_EXTERNAL_DEVICE as the frameBuffer parameter.


Note: Instead of using CL_CONTINUOUS_NONBLOCK, you can call clCompress() from a separate thread within the program. clCompress() does not return until the transfer is complete.

See cosmo_capture.c in /usr/people/4Dgifts/examples/dmedia/dmrecord for an example of capturing external video through Cosmo Compress.

Video fields entering Cosmo Compress from the direct video connection are captured into an array of field buffers. The field buffers support field widths from 640 to 768 and field heights from 16 to 336. Field dimensions depend on the video timing, as shown in Table 24-4.

Table 24-4 shows video field dimensions for the video formats supported by Cosmo Compress.

Table 24-4. Cosmo Compress Video Field Dimensions

Video Format

WIdth (pixels)

Height (pixels)

NTSC

640

243

PAL

768

288

CCIR(525)

720

243

CCIR(625)

720

288

Lines in the field buffers following the end of valid video data are filled with indeterminate data (that is, they are not blacked out).

When the compressed image height is less than the height of the incoming video fields, the video fields are clipped from the bottom before they are sent to the compressor. When the compressed image height is greater than the height of the incoming video fields, additional lines of indeterminate data are appended to the valid video data before the data is sent to the compressor.


Note: NTSC fields have a height (243 pixels) which is not a multiple of 8. For NTSC capture, you can choose to have your application either throw away 3 lines from the bottom of each field (240 pixel height) or append 5 extra lines to the bottom of each field (248 pixel height) prior to compression.

You can scale the captured image to half-size before compressing it. This allows for an additional increase in data compression by factor of 4.

Specify vertical decimation by setting the compressed image height (CL_INTERNAL_IMAGE_HEIGHT) to half the size of the uncompressed image height (CL_IMAGE_HEIGHT). Compressed image heights can range from 16 to 168 and uncompressed image heights can range from 32 to 336.

Specify horizontal decimation by setting the compressed image width (CL_INTERNAL_IMAGE_WIDTH) to half the size of the uncompressed image width (CL_IMAGE_WIDTH) as indicated in Table 24-5.

Table 24-5. Cosmo Compress Field Widths for Compression With Decimation

Video Format

CL_IMAGE_WIDTH (pixels)

CL_INTERNAL_IMAGE_WIDTH (pixels)

NTSC

640

320

PAL

768

384

CCIR(525)

720

360

CCIR(625)

720

360

During video compression from an external device, CLimageInfo.imagecount is initialized to 1 when the first field is received by the compressor after calling clCompress(). The count advances when a new field arrives. If the compression data buffer fills up, then a field will be dropped, but the imagecount continues to increase. An application can thus detect a dropped field by noticing a jump in the imagecount field of more than one. The ustime indicates the time the uncompressed field entered the compressor.

Memory-to-Video Decompression

The connections for decompressing from memory to an external video are set up similar to those for capturing video, except that a decompressor is opened. See clInit.c in /usr/people/4Dgifts/examples/dmedia/dmplay for example code that initializes the CL for JPEG decompression (optionally through Cosmo Compress) from memory to external video.

Video playback of the decompressed frames requires media synchronization. See dmplay.c and streamDecompress.c in /usr/people/4Dgifts/examples/dmedia/dmplay for more information.

Uncompressed fields leaving the JPEG decompressor may optionally be scaled up by a factor of 2 in the horizontal and/or vertical dimensions. NTSC, PAL or CCIR(525)/CCIR(625) fields are then scanned out of the array of field buffers. Horizontal scaling is performed by pixel replication, vertical scaling is performed by line doubling. If the uncompressed fields leaving the decompressor have fewer lines than the field height required by the NTSC, PAL or CCIR(525)/CCIR(625) connection (after optional pistoling), additional lines of indeterminate (not blacked out) data will be scanned out of the field buffers to pad out bottoms of the uncompressed images. If the uncompressed fields leaving the decompressor have more lines than the NTSC/PAL/CCIR(525)/CCIR(625) field height (after optional pistoling), lines will be clipped from the bottom of the uncompressed images.

Specify horizontal scaling by setting the uncompressed image width (CL_IMAGE_WIDTH) that is twice the compressed image width (CL_INTERNAL_IMAGE_WIDTH) as indicated in Table 24-6.

Table 24-6. Cosmo Compress Field Widths for Decompression

Video Format

CL_IMAGE_WIDTH (pixels)

CL_INTERNAL_IMAGE_WIDTH (pixels)

NTSC

640

320

PAL

768

384

CCIR(525)

720

360

CCIR(625)

720

360

Specify vertical scaling by setting the uncompressed image height (CL_IMAGE_HEIGHT) to twice the size of the compressed image height (CL_INTERNAL_IMAGE_HEIGHT). Compressed image heights can range from 16 to 168 and uncompressed image heights can range from 32 to 336.

During video decompression to an external device, CLimageInfo.imagecount reflects the count of fields sent by the application to the decompressor. The ustime indicates the time that field left the decompressor. In certain situations, fields are repeated on output, in which case the imagecount will remain the same, but the ustime will increase. Cosmo Compress decompression has a 1 frame delay through Galileo/IndyVideo before the field actually leaves the machine.

When transferring to or from external video, the video can be played continuously (default) or single-stepped a field or frame at a time. In either mode, the frame output is composed of either a single field replicated twice or two different fields. Specify the frame control by setting CL_COSMO_VIDEO_TRANSFER_MODE.

For continuous transfer, set CL_COSMO_VIDEO_TRANSFER_MODE to CL_COSMO_VIDEO_TRANSFER_AUTO_1_FIELD for the first field in a frame, and CL_COSMO_VIDEO_TRANSFER_AUTO_2_FIELD for the second field in a frame.

For manual control, set CL_COSMO_VIDEO_TRANSFER_MODE to CL_COSMO_VIDEO_TRANSFER_MANUAL_1_FIELD, and CL_COSMO_VIDEO_TRANSFER_MANUAL_2_FIELD.

In manual video transfer mode, the output frame can be set to either advance or repeat the current frame or field, as specified by CL_COSMO_VIDEO_FRAME_CONTROL.

You can control compression or decompression with CL_COSMO_CODEC_CONTROL. Setting CL_COSMO_CODEC_CONTROL to CL_COSMO_STOP halts compression or decompression. If clCompress() or clDecompress() was called with CL_CONTINUOUS_BLOCK, the function returns. If clCompress() or clDecompress() was called with CL_CONTINUOUS_NONBLOCK, the associated thread terminates.

Controlling JPEG Compressed Image Quality

JPEG is a tunable algorithm—you can trade quality for compression ratio and vice-versa. You can specify a hint (CL_COMPRESSION_RATIO) for an approximate compression ratio or you can set more explicit quality factors, as described next.

The source image is compressed in three basic steps.

  1. Data is transformed from spatial to frequency form in eight-by- eight blocks using a discrete cosine transform (DCT).

  2. The frequency coefficients are filtered down by a linear quantization.

  3. The coefficients are Huffman-encoded into a bit stream.

The process is reversed for decompression.

The quantization step controls the trade-off between image quality and size. A table called the JPEG quantization table is used to scale each of the 64 DCT coefficients. The luminance (Y) and the chrominance (Cr and Cb) components each use a separate table.

The CL provides two methods for controlling image quality from these quantization tables. You can specify an overall JPEG quality factor (CL_JPEG_QUALITY_FACTOR) for scaling the default JPEG quantization tables or you can manually set the quantization tables CL_JPEG_QUANTIZATION_TABLES.

The JPEG algorithm does not allow you to specify exact compression ratios (or bit rate targets), so the CL_EXACT_COMPRESSION_RATIO parameter is not supported by the CL JPEG codecs.

Specifying a JPEG Quality Factor

You can use the CL_JPEG _QUALITY_FACTOR parameter to specify a JPEG quantization table scale factor that represents a rough percentage of the image detail preservation. This is one method to control the image loss and therefore the compression ratio for the Cosmo Compress JPEG algorithm.

Each time the quality factor is set, the reference quantization tables are scaled and downloaded into the codec. The formula used to obtain the scale factor is:

scalefactor = 50/quality          (quality < 50)
scalefactor = 2 - 2*quality/100;  (otherwise)

The default quality is CL_JPEG_QUALITY_DEFAULT, which represents a good-quality compressed image. A quality factor of 1 results in coarse quantization, a high compression ratio, and very poor image quality.
A quality factor of 100 results in the finest possible quantization, a low compression ratio (perhaps even image expansion), and near-perfect image quality. The most useful quality factor is typically in the range of 25–95.

To bypass scaling, specify CL_JPEG_QUALITY_NO_SCALE.

Defining and Using Custom JPEG Quantization Tables

You can customize the JPEG quantization tables by using the CL_JPEG_QUANTIZATION_TABLES parameter. To set the tables, specify an unsigned short *qtables[4] argument. For each j, qtables[j] must either be NULL or point to a unsigned short[64] area of memory that represents a JPEG-baseline quantization table in natural scanning order. These custom tables are then stored as reference tables, and then scaled versions of them based on the current CL_JPEG_QUALITY_FACTOR are downloaded into the codec, becoming the tables associated with the ID j.

When getting the value of CL_JPEG_QUANTIZATION_TABLES, the CL allocates the required memory and returns the currently used tables, as indicated by CL_JPEG_COMPONENT_TABLES, scaled by the value of CL_JPEG_QUALITY_FACTOR. Your application is responsible for freeing the memory allocated to return these tables.

You can specify the quantization tables on a per-component basis, by using the CL_JPEG_COMPONENT_TABLES parameter. It specifies the IDs of the AC Huffman table, DC Huffman table, and quantization table to be used for each component. Currently, you cannot change this parameter for Cosmo Compress—it is set up for YUV422 processing. This setting uses AC Huffman table 0, DC Huffman table 0, and quantization table 0 for component 0; AC huffman table 1, DC huffman table 1, and quantization table 1 for components 1 and 2.