Audio and video compression technology is constantly evolving. The Compression Library provides the flexibility to evolve with this changing environment by letting you customize and expand libcl. This chapter explains how to add your own algorithms to the library and how to add new state parameters to these algorithms to provide capabilities that are not addressed by the standard set of parameters. To ensure capability across applications, you must use the existing interface paradigm for your custom implementations; you cannot add to or change the API.
In this chapter:
“Adding Custom Algorithms to the Compression Library” explains how to add algorithms to the CL.
“Adding Custom Parameters to the Compression Library” explains how to add parameters to the CL.
For compatibility, an algorithm that is to be added to the Compression Library must meet these requirements:
Provide support for all three types of interfaces (single-image, sequential, and buffered) through the clCompress() and clDecompress() entry points.
Support interfaces for the query routines clGetParams() and clSetParams().
Provide the ability to specify the worst-case size of the compressed data through the CL_COMPRESSED _BUFFER_SIZE parameter so that the application can then allocate buffers of appropriate size.
Report errors using clError().
Use clAddAlgorithm() to add your own compression algorithms to libcl. clAddAlgorithm() adds compression algorithms to the library by passing function pointers to routines that are unique for each algorithm. When you call this added algorithm, some preprocessing is done, then the routines that have been passed to clAddAlgorithm() are called.
The function prototype for clAddAlgorithm() is:
int clAddAlgorithm ( char *name, int type,
int maximumHeaderSize,
FunctionPtr openCompressor,
FunctionPtr compress,
FunctionPtr closeCompressor,
FunctionPtr openDecompressor,
FunctionPtr decompress,
FunctionPtr closeDecompressor,
FunctionPtr readHeader,
FunctionPtr queryScheme,
FunctionPtr queryLicense,
FunctionPtr getParams,
FunctionPtr setParams,
int *compressionScheme)
|
where:
| maximumHeaderSize | | |
| name | is a pointer to a string that contains the name of the algorithm. | |
| type | is the type of the algorithm (CL_AUDIO or CL_VIDEO). | |
| openCompressor | is a pointer to the function that opens a compressor for the new algorithm. The function must have the same arguments as clOpenCompressor(). | |
| compress | is a pointer to the function that compresses for the new algorithm. The function must have the same arguments as clCompress(). | |
| closeCompressor |
| |
| openDecompressor |
| |
| decompress | is a pointer to the function that decompresses for the new algorithm. The function must have the same arguments as clDecompress(). | |
| closeDecompressor |
| |
| readHeader | is pointer to the function that reads the stream header for the new algorithm. The function must have the same arguments as clReadHeader(). | |
| queryScheme | is a pointer to the function that identifies the scheme from the stream header for the new algorithm. The function must have the same arguments as clQueryScheme(). | |
| queryLicense | is a pointer to the function that determines whether there is a license for the new algorithm. The function must have the same arguments as clQueryLicense(). | |
| getParams | is a pointer to the function that gets compressor or decompressor parameters for the new algorithm. The function must have the same arguments as clGetParams(). | |
| setParams | is a pointer to the function that sets compressor or decompressor parameters for the new algorithm. The function must have the same arguments as clSetParams(). | |
| compressionScheme |
|
Argument bounds checking is performed before these functions are called. For example, if clCompress() is called, each argument is checked for validity before passing control to the function passed to clAddAlgorithm().
Added algorithms must support interfaces to clGetParams() and clSetParams(), which are supplied to clAddAlgorithm(). The algorithm implementation is notified of changes to, or requests for, parameter values through these routines. This allows complete control of parameters to constrain or report errors upon setting or to calculate only when requested.
Algorithms and parameters, once added, will show up when queried with clQueryAlgorithms() and clQueryParams() respectively.
The algorithm implementation is required to specify the worst-case size of the compressed data for a frame through the CL_COMPRESSED_BUFFER_SIZE parameter. This value must be calculated every time it is requested, using the current value of other parameters such as CL_IMAGE_WIDTH, CL_HEIGHT, and CL_INTERNAL_FORMAT.
An algorithm can also specify a value for its most natural number of frames to process at a time in the CL_BLOCK_SIZE parameter.
The number of blocks that need to be processed before decompressed data begins to emerge is specified in the CL_PREROLL parameter. For algorithms with a fixed compression ratio, this may allow the application to use the sequential interface.
Use clSetUnique() and clGetUnique() to allow the algorithm implementation to store and retrieve algorithm-specific information that is associated with each instantiation of a compressor or decompressor.
When clOpenCompressor() or clOpenDecompressor() is called, the implementation sets up the unique pointer that gets stored associated with the compressor handle. Other functions that require the information get it using that handle. clSetUnique() returns the previous unique pointer. clGetUnique() returns the current unique pointer.
The function prototypes are:
void * clSetUnique(CLhandle handle, void *unique) |
void * clGetUnique(CLhandle handle) |
where:
| handle | is a handle to a compressor or decompressor. | |
| unique | is a pointer to unique data that is associated with handle. |
When adding an algorithm, use clSetMin() and clSetMax() to set its minimum and maximum values, respectively. For example, you may need to set the bounds that define the legal range of the compression ratio. These settings take effect when either clOpenCompressor() or clOpenDecompressor() is called.
When adding an algorithm, use clSetDefault() to set defaults. For example, you may need to specify a default compression ratio. These defaults take effect when either clOpenCompressor() or clOpenDecompressor() is called.
Example 26-1 demonstrates how to add algorithms to the Compression Library.
Example 26-1. Adding Algorithms to the Compression Library
#include <cl.h>
...
int scheme;
...
/* Add the new algorithm */
clAddAlgorithm(“New Algorithm”, CL_VIDEO,
NEW_ALGORITHM_MAX_HEADER_SIZE,
OpenNewCompressor, CompressNew, CloseNewCompressor,
OpenNewDecompressor, DecompressNew, CloseNewDecompressor,
ReadNewHeader, QueryNewScheme, QueryLicense, GetNewParams,
SetNewParams, &newScheme);
/* Compress a series of frames (same as always) */
clOpenCompressor(newScheme, &handle);
for(i = 0; i < numberOfFrames; i++)
{
/* Get a frame from somewhere */
...
clCompress(handle, i, 1, frameBuffer, &compressedDataSize,
compressedData);
/* Write the compressed data to somewhere else. */
...
}
clCloseCompressor(handle);
|
When you add an algorithm, you must mirror the normal use of the buffer management calls, that is, the calls for compression and decompression are swapped and the calling order is reversed. Example 26-2 sets up decompression buffering for added algorithms.
Example 26-2. Decompression Buffering
until numberOfFrames frames are decompressed:
until space for a frame is available:
actualNumberOfFrames = clQueryFree(frameHdl, 1, &frameBuffer, &wrap);
until a frame is decompressed (and the compressed data is available):
actualBufSize = clQueryValid(dataHdl, bufSize, &buf, &wrap);
/* Decompress the data in “buf” and place the result in “frameBuffer” *
actualSize = clUpdateTail(dataHdl, actualBufSize);
actualNumberOfFrames = clUpdateHead(frameHdl, numberOfFrames);
|
Example 26-3 sets up decompression buffering for added algorithms.
Example 26-3. Compression Buffering
until numberOfFrames frames are compressed:
until a frame is available:
actualNumberOfFrames = clQueryValid(frameHdl, 1, &frameBuffer, &wrap);
until a frame is compressed (and space for the compressed data is available):
actualSize = clQueryFree(dataHdl, size, &buf, &wrap);
/* Compress the frame in “frameBuffer” and place the result in “buf” */
actualLen = clUpdateHead(dataHdl, len);
actualNumberOfFrames = clUpdateTail(frameHdl, numberOfFrames);
|
When clDecompress() is called with non-NULL pointers for the compressedData or frameBuffer arguments, the data is available through the buffer management calls, so no special code is required for that case; however, care must be taken not to wait for data that will never arrive. For example, if insufficient data is passed into the compressedData or frameBuffer parameters, you don't want the application to block and wait forever for data.
To avoid this situation for compressedData, you can use clReadData(), which provides an interface that removes the need for the application to know about the discontinuity of the compressed data caused by using a ring buffer. Its function prototype is:
int clReadData(CLbufferHdl bufferHdl, int requestedDataSize, void **compressedData) |
where:
bufferHdl | is a handle to a compressor/decompressor. |
requestedDataSize | is the size of the requested data. |
compressedData | is a pointer to the returned pointer to the compressed data. |
Because it is often not known what the size of the compressed data is, clReadData() allows the algorithm to request data of arbitrary size, such as the next piece of data that it knows it needs. When the requested data crosses a discontinuity, it is automatically pieced together in a temporary buffer. A pointer to this temporary buffer is returned. If the size of the requested data is larger than what is present in the buffer, the routine blocks until the data arrives. Alternatively, if the compressed data were passed directly to clDecompress(), no more data would arrive, no matter how long it waited and whatever data was available would be returned.
clReadData() calls clQueryValidData() and clUpdateTail(dataHdl). It blocks (unless the compressed data was passed directly to clDecompress()) until the requested amount of data has accumulated and, if necessary (at the end of the ring buffer), copies the data into a temporary buffer, to guarantee one contiguous buffer.
clReadData() returns the actual number of bytes read. clDone() returns the actual number of bytes updated. The requested data size is always returned unless there is an error. If the data requested crosses the discontinuity from the end to the beginning of the ring buffer, a temporary buffer is automatically created, the data from the ring buffer is copied to it, and its address is returned in the compressedData argument.
An algorithm has two parts: the compressed data and the bitstream that encapsulates it. For some algorithms, such as JPEG and MPEG, the bitstream is fairly complex and must be parsed in very small segments. clReadData() is designed to be very efficient and can be used to read many small segments of a few bytes if so desired.
Use clDone() to update the consumed data read by clReadData(). Its function prototype is:
int clDone(CLbufferHdl bufferHdl, int amountToUpdate) |
where:
| bufferHdl | is a handle to a compressor or decompressor. | |
| requestedDataSize |
| |
| compressedData |
| |
| amountToUpdate |
|
In each call to clReadData(), clUpdateTail() is called to release data from the previous call to clReadData(), and clQueryValid() is called to get the new data. clDone() is used at the end of the decompress routine (just before returning) to call clUpdateTail() for data used from the last read.
New algorithms should report errors with clError(). Generally, the format string starts with the routine name within which the error occurred, followed by a description of the error.
The buffer architecture for adding algorithms is shown in Figure 26-1. The routines called by the compressor and decompressor are shown.
You can add audio or video compression parameters to libcl. This is useful when using clAddAlgorithm() to add a new algorithm that uses parameters that don't exist in the default set of compression parameters. The application uses the new parameters as it would any of the other compression parameters. The functions for the new compression algorithm access the parameters in the same way as the application.
Use clAddParam() to add parameters to the library. Its function prototype is:
int clAddParam(int scheme, char *name, int type, int min, int max, int initial, int *paramID) |
where:
| scheme | is the compression scheme to add a parameter to. | |
| name | is a pointer to a string that contains the name of the parameter. | |
| type | is the type of the parameter: CL_ENUM_VALUE, CL_RANGE_VALUE, CL_POINTER, CL_FLOATING_ENUM_VALUE, or CL_FLOATING_RANGE_VALUE. | |
| min | is the minimum value of the parameter. | |
| max | is the maximum value of the parameter. | |
| initial | is the default value of the parameter. | |
| paramID | is a pointer to an int value to receive the compression parameter identifier. |
The code fragment in Example 26-4 adds a new video algorithm to the CL.
Example 26-4. Adding Parameters to the Compression Library
#include <cl.h>
int paramID;
...
/* Add a new algorithm */
clAddAlgorithm(“New Algorithm”, CL_VIDEO,
NEW_ALGORITHM_MAX_HEADER_SIZE,
OpenNewCompressor, CompressNew, CloseNewCompressor,
OpenNewDecompressor, DecompressNew, CloseNewDecompressor,
ReadNewHeader, QueryNewScheme, GetNewParams, SetNewParams,
&newScheme);
/* Add the new parameter */
clAddParam(newScheme, “New Parameter”, CL_RANGE_VALUE, 0, 100,
75, ¶mID);
/* Compress a series of frames (same as always) */
clOpenCompressor(newScheme, &handle);
clSetParam(handle, paramID, 55);
...
|