Chapter 2. Programming With the Compression Library

This chapter describes how to use the Compression Library API to compress and decompress image and video data. The CL provides three interfaces for successively more complex compression:


Note: Using the CL with video options is explained in detail in Chapter 4, “Using the Compression Library With OCTANE Compression.”

In this chapter:

Table 2-1 lists calls explained in this chapter.

Table 2-1. Compression Library Calls

Compression and Decompression

Buffers

Miscellaneous

clCompress()
clDecompress()
clOpenCompressor()
clOpenDecompressor()
clCloseCompressor()
clCloseDecompressor()
clCompressImage()
clDecompressImage()

clCreateBuf()
clDestroyBuf()
clQueryBufferHdl()
clQueryHandle()
clQueryFree()
clUpdateHead()
clUpdateTail()
clDoneUpdatingHead()
clQueryValid()
clQuery()
clUpdate()

clSetErrorHandler()
clQuerySchemeFromName()
clQueryScheme()
clGetParams()
clSetParams()
clReadHeader()
clQueryMaxHeaderSize()


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 2-1 demonstrates how to silence error reporting for a section of code.

Example 2-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 */
...



Note: If an application attempts to decompress data that is not valid JPEG data, the decompressor can hang.


Opening a Compression Session

Unlike the Cosmo Compress™ option, the OCTANE Compression option does not have a predefined scheme value; that is, no scheme pound define is specified for OCTANE Compression. Instead, applications use clQuerySchemeFromName() to query the CL whether a scheme with the name impact is available in the system.

If the scheme is available, the return from this function specifies the scheme identifier to pass to the CL routines. As other schemes are added to the Compression Library on a specific workstation, the actual value assigned to OCTANE Compression can change.

Example 2-2. Querying the Scheme Name

#include <cl.h> 
int scheme; 
CLhandle clHandle; 

scheme = clQuerySchemeFromName (CL_ALG_VIDEO, "impact"); 
if (scheme < 0) { 
    fprintf(stderr, "compression scheme ;'impact' is not configured\n"); 
    return; 
 
} 
clOpenCompressor (scheme, &clHandle); 

In modes where the CL and VL interact to control the OCTANE Compression hardware, applications must follow an ordering of when events are requested. For all operations involving a CL_EXTERNAL_DEVICE, the order of startup is:

  1. vlBeginTransfer()

  2. clCompress() or clDecompress()

The call to clCompress() or clDecompress() actually starts the device operating. If the vlBeginTransfer() is initiated after the CL operation, indeterminate data is captured or the first fields of output are lost.

Using the Still Image Interface

Table 2-2 lists the calls explained in this section.

Table 2-2. Still Image Interface Calls

Compression

Decompression

Miscellaneous

clCompressImage()

clDecompressImage()

clQueryMaxHeaderSize()

The single image method is designed to make still image compression as simple as possible. The still image interface 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.

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

  • CL_YUV422DC

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 framebuffer 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 used 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 2-3 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 2-3. 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);



Note: If an application attempts to decompress data that is not valid JPEG data, the decompressor can hang.


Using the Sequential Frame Interface

Table 2-3 lists the calls explained in this section.

Table 2-3. Sequential Frame Interface Calls

Compression

Decompression

Miscellaneous

clOpenCompressor()

clOpenDeompressor()

clGetParams()

clCloseCompressor()

clCloseDecompressor()

clSetParams()

clCompress()

clDecompress()

clQueryScheme()

clCompressImage()

clDecompressImage()

clReadHeader()

 

 

clQueryMaxHeaderSize()

The sequential interface is designed for video-streaming applications where the input is live, or where there is no control over playback and 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.

This section describes how to work with sequential frames of 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. See Chapter 5 of the Digital Media Programming Guide (007-1799-060 or later) for a complete description of buffers

Compressing a Sequence of Frames

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

To compress a sequence of frames, follow these steps:

  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 framebuffer 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, or either CL_CONTINUOUS_BLOCK or CL_CONTINUOUS_NONBLOCK to continue compression until either the framebuffer is marked as 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” on page 23. An error is reported if no buffer exists. Some compressors allow a value of CL_EXTERNAL_DEVICE, indicating a direct connection to an external 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” on page 23.

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 framebuffer 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.


Note: Parameters are explained in detail in Chapter 6, “Using Compression Library 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 2-4 demonstrates how to compress a series of frames using the CL_MVC1 algorithm. A compressor is opened, and 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 2-4. 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 video streams requires the use of a decompressor. A decompressor is an abstraction that modularizes decompression operations.

To decompress a sequence of frames, follow these steps:

  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 2-4 for a list of typical header contents, and Table 2-5 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 2-4.

Table 2-4. 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



Note: For complete information on algorithms used with the OCTANE Compression option, see Chapter 7, “Using Compression Library Algorithms.” For information on parameters, see Chapter 6, “Using Compression Library Parameters.”

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

Table 2-5. Additional Video Stream Header Contents

Parameter

Information Supplied

CL_IMAGE_WIDTH

Width

CL_IMAGE_HEIGHT

Height

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

Example 2-5. 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); 
read(inFile, header, headerSize); 
} 


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 framebuffer 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, or either CL_CONTINUOUS_BLOCK or CL_CONTINUOUS_NONBLOCK to continue decompression until either the framebuffer is marked as 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” on page 23.

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 framebuffer 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 video source. Using a NULL argument invokes the buffered interface that is described in “Using the Buffering Interface” on page 23. 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 2-6 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 2-6. 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

Table 2-6 lists the calls explained in this section.

Table 2-6. Buffering Interface Calls

Creating and Destroying Buffers

Managing Buffers

clCreateBuf()

clQueryFree()

clDestroyBuf()

clUpdateHead()

clQueryBufferHdl()

clUpdateTail()

clQueryHandle()

clQueryValid()

 

clDoneUpdatingHead()

 

clQuery()

 

clUpdate()

The buffered interface is designed for

  • VCR-like control over the video stream

  • maximum efficiency by buffering compressed data and uncompressed frames

  • blocking and nonblocking access

  • transparent buffering for hardware acceleration or for multiprocessor operation

  • multi-threaded applications

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

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.

Buffers 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 multi-threaded 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 2-1 is a conceptual drawing of a ring buffer.

Figure 2-1. Ring Buffer

Figure 2-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.


Note: The OCTANE Compression hardware is optimized for use in the asynchronous ring-buffer modes of operation of the CL. Both the compressed and uncompressed channels to main memory are buffered; flushing those buffers slows processing. Applications that require real-time operation must use the ring-buffer modes of the CL.

DMA operations on OCTANE Compression vary depending upon the number of bytes available to transfer. The device driver attempts to transfer data in large blocks, but can step it down to less efficient, smaller block sizes to transfer data out of a ring buffer completely. The minimum transfer size supported by the OCTANE Compression option is eight bytes.

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 framebuffer 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 framebuffer

  • 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 or 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 2-7 demonstrates how to create and use an internal buffer.

Example 2-7. 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 2-8 demonstrates how to create and use an external buffer.

Example 2-8. 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 framebuffer 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 2-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) framebuffer.

The block size for the uncompressed framebuffer 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 2-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 internally of whether the buffer is empty or full.

In the first frame of Figure 2-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 2-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 2-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 2-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 2-2. Snapshots of Buffer State During Producing and Consuming Processes

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

Figure 2-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 2-3. Flow of Data in a Buffered Compression and Decompression Scheme

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

Hardware Buffer Flushing and Latency

When an image is compressed or decompressed in memory-to-memory modes, it can be partially contained in the hardware for some time. The device driver does not notify the application that data is available until all data for a particular field has made its way to memory and a complete processor cache line has been completed.

This situation is of concern only for applications that use the asynchronous ring-buffer mode of operation, and do not wish to close the compressor or decompressor (codec). When the codec is closed, any buffered data is processed and flushed to the application.

A portion of the image is usually trapped in hardware buffering until either the compressor is closed or a subsequent image flushes this image portion out. If the application is decompressing to a CL_EXTERNAL_DEVICE, the application should ensure that the last compressed image sent to the board is completely decompressed. When the application wants to explicitly flush an image out to the video portion of the board, it should send 16 bytes of the value 255 (0 × FF), as shown in Example 2-9.

Example 2-9. Flush Compressed Data to CL_EXTERNAL_DEVICE

...
/* get an image from somewhere and put into ring buffer */
/* send an image of data to the decompressor */
clUpdateHead( bufferHandle, size_of_image );
/* flush data though JPEG decompressor */
avail = clQueryFree( bufferHandle, 16, &free, &wrap );
if (prewrap > 16) {
    memset( free, 0xFF, 16 );
    clUpdateHead( bufferHandle, 16 );
}
else {
    /* handle wrapped CL buffers */
}



Note: This flush operation is necessary only when the application expects some time between the images that are decompressed. If the application is immediately sending another compressed image, that image flushes the previous image through the decompressor.


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 2-10 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 framebuffer, then releasing the consumed frames.

Example 2-10. 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 framebuffer. If space for a frame exists in the framebuffer, then the routine begins decompressing directly to the framebuffer. 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. When it finishes decompressing a frame, it updates the framebuffer pointers and returns. clDecompress() does not return until decompression is complete or until an error occurs.

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

clQueryValid() returns the pointer into the frame ring buffer. clUpdateTail() is required to free the internal framebuffer 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 framebuffers 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 does 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, and

  • 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 2-11 demonstrates how to implement nonblocking playback.

Example 2-11. 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 does not get any larger, so the amount remaining in the buffer must be consumed.

Creating a Buffered Record Application

The code fragment in Example 2-12 demonstrates how to use buffers for recording.

Example 2-12. 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 framebuffer, 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 framebuffer, but does not copy the data.

clCompress() reads from the framebuffer and writes to the data buffer. If a frame exists in the framebuffer, then the routine begins compressing directly from the framebuffer. 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 framebuffer 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 or 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 2-13 demonstrates how to use buffers for nonblocking recording.

Example 2-13. 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 does 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(). To use data input buffering, clDecompress() receives NULL for compressedData. To use frame output buffering, clDecompress() receives NULL for frameBuffer.

clCompress() reads from the framebuffer and writes to the data buffer. If a frame exists in the framebuffer, then the routine begins compressing directly from the framebuffer. 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 framebuffer pointers and returns. clCompress() does not return until compression is complete or until an error occurs.

Creating Buffered Multiprocess Record and Play Applications

Consuming, compressing or decompressing, and producing can be separated into different sets of calls. The most powerful use of the buffering interface, however, 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 2-14 demonstrates how to implement multiprocess playback. The functions in boldface can be implemented as separate processes.

Example 2-14. 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 2-15 demonstrates how to use buffers for multiprocess recording. The functions in boldface can be implemented as separate processes.

Example 2-15. 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);

Implementing functions as separate processes 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 blocks 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.