Please note as of Wednesday, August 15th, 2018 this wiki has been set to read only. If you are a TI Employee and require Edit ability please contact x0211426 from the company directory.

Codec Engine Application Developers Guide

From Texas Instruments Wiki
Jump to: navigation, search

Note: This article was sourced from (and intends to supercede!) SPRUE67.

The Codec Engine is a set of APIs that you use to instantiate and run XDAIS algorithms. A VISA interface is provided as well for interacting with XDM-compliant XDAIS algorithms.

The intended audience for this section is the embedded-OS application developer needing to create and interact with XDAIS algorithms.

Contents

Overview

The Codec Engine has Core Engine APIs and VISA APIs that are available to the Application Author. The following table shows the Core Engine API modules:

Codec Engine Modules
Description Module Abbreviation Package Name Header File(s) See Section
Initialization function CERuntime_ ti.sdo.ce CERuntime.h Codec Engine Setup Code
Codec Engine Runtime Engine_, Server_ ti.sdo.ce Engine.h, Server.h The Core Engine APIs
OS Abstraction Layer for memory Memory_ ti.sdo.ce.osal Memory.h What Happens to DSP Memory Issues?

In addition to modules that perform setup and teardown activities, a memory abstraction module provides support for applications that use Codec Engine in a GPP+DSP system.

The following table shows the VISA API modules:

VISA Modules
Description Module Prefix Package Name Header File(s)
Video Encoder Interface (deprecated) VIDENC_ ti.sdo.ce.video videnc.h
Video Encoder Interface VIDENC1_ ti.sdo.ce.video1 videnc1.h
Video Encoder Interface VIDENC2_ ti.sdo.ce.video2 videnc2.h
Video Decoder Interface (deprecated) VIDDEC_ ti.sdo.ce.video viddec.h
Video Decoder Interface (deprecated) VIDDEC1_ ti.sdo.ce.video1 viddec1.h
Video Decoder Interface VIDDEC2_ ti.sdo.ce.video2 viddec2.h
Video Decoder Interface VIDDEC3_ ti.sdo.ce.video3 viddec3.h
Image Encoder Interface (deprecated) IMGENC_ ti.sdo.ce.image imgenc.h
Image Encoder Interface IMGENC1_ ti.sdo.ce.image1 imgenc1.h
Image Decoder Interface (deprecated) IMGDEC_ ti.sdo.ce.image imgdec.h
Image Decoder Interface IMGDEC1_ ti.sdo.ce.image1 imgdec1.h
Speech Encoder Interface (deprecated) SPHENC_ ti.sdo.ce.speech sphenc.h
Speech Encoder Interface SPHENC1_ ti.sdo.ce.speech1 sphenc1.h
Speech Decoder Interface (deprecated) SPHDEC_ ti.sdo.ce.speech sphdec.h
Speech Decoder Interface SPHDEC1_ ti.sdo.ce.speech1 sphdec1.h
Audio Encoder Interface (deprecated) AUDENC_ ti.sdo.ce.audio audenc.h
Audio Encoder Interface AUDENC1_ ti.sdo.ce.audio1 audenc1.h
Audio Decoder Interface (deprecated) AUDDEC_ ti.sdo.ce.audio auddec.h
Audio Decoder Interface AUDDEC1_ ti.sdo.ce.audio1 auddec1.h
Universal Interface UNIVERSAL_ ti.sdo.ce.universal universal.h
Video Analytics Interface (beta) VIDANALYTICS_ ti.sdo.ce.vidanalytics vidanalytics.h
Video Transcode Interface VIDTRANSCODE_ ti.sdo.ce.vidtranscode vidtranscode.h

The VISA interfaces provided with CE include support for all XDM interfaces.

Example "Copy" codecs complying with the XDM 1.x interfaces are provided with the XDAIS 5.21 (or newer) product. CE utilizes those codecs in some of its examples. For example, the video1_copy example utilizes the VIDENC1 and VIDDEC1 VISA interfaces to communicate with the videnc1_copy and viddec1_copy codecs.

The package name corresponds to the path your application must use to reference the header file it includes to use a particular module. For example, the speech encoder module, SPHENC1, has a package name of ti.sdo.ce.speech1. The #include statement for this module is:

#include <ti/sdo/ce/speech1/sphenc1.h>

Your application uses the Engine module to open and close instances of the Codec Engine. You can also use this module to get information about memory use and CPU loading. See the section The Core Engine APIs.

Once your application has opened an instance of the Codec Engine, you use the modules in the VISA packages (for example, VIDENC for video encoding) to create instances of various algorithms. Using the handle for the algorithm instance you create, you then use the same module to run or otherwise control the algorithm. See the section The VISA Classes: Video, Image, Speech, Audio.

Reference documentation for the Codec Engine APIs is installed with the Codec Engine software at $(CE_INSTALL_DIR)/docs/html, and also available online. This chapter provides an overview of the APIs and how you use them. For details about the calling syntax, see the reference documentation.

The Core Engine APIs

The Codec Engine has a "core" module called "Engine". Your application uses this module to open and close Engine instances. Multi-threaded applications must either serialize access to a shared Engine instance or create a separate Engine instance for each thread.


Note: Be aware that Engine handles are not thread-protected. Each thread that uses an Engine instance should perform its own Engine_open() call and use its own Engine handle. This protects each Engine instance from access by other threads in a multi-threaded environment.


You can also use the Engine module to get information about memory use and CPU loading.

The APIs for the Engine module are described in detail in the CE API Reference Guide. A brief summary of commonly used Engine_* APIs is here:

  • Engine_open() - Open an Engine.
  • Engine_close() - Close an Engine.
  • Engine_getLastError() - Get the error code of the last failed operation.
  • Engine_getUsedMem() - Get Engine memory usage.
  • Engine_getNumAlgs() - Get the number of algorithms in an Engine.
  • Engine_getAlgInfo() Get information about an algorithm.
  • Engine_getCpuLoad() - Get Server's CPU usage in percent. (Note, this API is now deprecated. Users should instead use Server_getCpuLoad())

After opening an Engine, you create algorithm instances using the VISA APIs described in the section The VISA Classes: Video, Image, Speech, Audio.

Reference documentation for the Codec Engine APIs is installed with the Codec Engine software at $(CE_INSTALL_DIR)/docs/html, as well as online here. For details about the calling syntax, see the reference documentation.

Codec Engine Setup Code

An application that will use the Codec Engine should include the following header files, where these directory paths are relative to the XDCPATH, which includes $(CE_INSTALL_DIR)/packages and $(XDC_INSTALL_DIR)/packages.

#include <xdc/std.h>
#include <ti/sdo/ce/Engine.h>
#include <ti/sdo/ce/CERuntime.h>

In addition, the application must include the header file for any VISA modules it uses. For example:

#include <ti/sdo/ce/audio1/auddec1.h>

Notice that the paths to the header files exactly correspond to the package paths shown in Table 4–2, Codec Engine Modules. All applications that use the Codec Engine must call CERuntime_init() before any other APIs.

In addition, when running a GPP+DSP application that uses Codec Engine, you must load any dependent drivers, e.g. DSP Link and CMEM. How these dependent modules are loaded depends on the GPP OS.

  • Linux - examine the examples/apps/system_files/*/loadmodules.sh and $(CE_INSTALL_DIR)/examples/build_instructions.html.
  • WinCE - examine $(CE_INSTALL_DIR)/examples/build_instructions_wince.html

Opening an Engine

When you open an Engine, you specify the name of the Engine you want to open. For example:

Engine_Handle hEngine;
Engine_Error err;
 
hEngine = Engine_open("auddec", NULL, &err);

Note: Be aware that Engine handles are not thread-protected. Each thread that uses an Engine instance should perform its own Engine_open() call and use its own Engine handle. This protects each Engine instance from access by other threads in a multi-threaded environment.


Engines are created and configured by your Engine Integrator in the config script (typically a .cfg file). These config scripts define which algorithms to configure and build into each Engine. See the page called Integrating a Codec Engine for information the Engine Integrator needs to create such Engines.

For example, on GPP+CPU systems (e.g. DM644x, DM6467, OMAP3, OMAP-L13x, etc.), algorithms may run locally (on the GPP) or "remotely" (on the DSP). For remote algorithms, the Engine transparently uses a "DSP Server" and the DSP Link transport to run the remote algorithms. When there are remote algorithms configured into an Engine, the first request to open that Engine (i.e., Engine_open()) results in the following underlying actions:

  • Power ON the DSP (if support is available and enabled). If enabled, this is handled by LPM (Local Power Manager), which is only supported on DM644x, OMAP2 and OMAP3)
  • Call the Link APIs needed to initialize the DSP and the transport. For DSPLink and BIOS 5 based systems: PROC_setup(), PROC_attach(), POOL_open(), PROC_load(), PROC_start(), and MSGQ_transportOpen(). For SysLink and BIOS 6 based systems: ProcMgr_open(), ProcMgr_attach(), ProcMgr_load(), and ProcMgr_start().
  • Perform the initial handshakes from the GPP side to the remote dispatcher on the DSP.

If the Engine_Handle returned by Engine_open() is NULL, the Engine could not be opened. If the errorcode parameter is non-NULL, the Engine_Error value contains more details - some commonly returned error codes include:

  • Engine_EOK - Success.
  • Engine_EEXIST - Name does not exist.
  • Engine_ENOMEM - Can't allocate memory.
  • Engine_EDSPLOAD - Can't load the DSP.
  • Engine_ENOCOMM - Can't create a communication connection to the DSP.
  • Engine_ENOSERVER - Can't locate the Server on the DSP.
  • Engine_ECOMALLOC - Can't allocate a communication buffer.

Your application can handle this error. For example:

ce = Engine_open(engineName, NULL, &errorCode);
if (ce == NULL) {
    printf("Error: could not open engine \"%s\"; Error code %d.\n", engineName, errorCode);
}

Closing an Engine

To close an Engine instance and free memory it uses, your application should call Engine_close(). For example:

Engine_close(ce);

You should do this only after you have deleted any algorithm instances created for this Engine.

For example, given the DM644x-based example described in the previous section with remote algorithms performed on the DSP, the last call to Engine_close() results in the following underlying actions:

  • Call the Link APIs needed to "finalize" the DSP and the transport. For DSPLink and BIOS 5 based systems: MSGQ_transportClose(), PROC_stop(), POOL_close(), PROC_detach(), and PROC_destroy(). For SysLink and BIOS 6 based systems: ProcMgr_stop(), ProcMgr_unload(), ProcMgr_detach(), and ProcMgr_close().
  • Power OFF the DSP (if support is available and enabled).

Adding Engines to the Engine Database (BIOS 6)

In BIOS 5 based versions of Codec Engine, all engines must be statically configured into the application’s .cfg file. BIOS 6 based versions of Codec Engine do not have this restriction, allowing engine names to be added at run-time, to the database of engines that can be opened.

To add an engine, use the function, Engine_add(), specifying the name of the engine and the name of the server image (if applicable) to load when the engine is opened. The example code below adds an engine named "universal_copy" with a remote server. The server has an algorithm that implements ti.sdo.ce.universal.IUNIVERSAL, that we will want to call from the host.

/* Include header file, ti.sdo.ce.universal, for stub functions */
#include <ti/sdo/ce/universal/universal.h>
#include <ti/sdo/ce/Engine.h>
 
Int main(Int argc, String argv[])
{
    ...
 
    /*
     *  The Engine_Desc structure will hold the engine name and server
     *  name that get passed to Engine_add().
     */
    Engine_Desc engDesc;
 
    /* Initialize the Engine_Desc object with default values. */
    Engine_initDesc(&engDesc);
 
    /* name of the engine that will be passed to Engine_open() */
    engDesc.name = "universal_copy";
 
    /*
     *  For SysLink based systems, the name of the DSP executable
     *  that will be loaded onto the remote processor when the engine
     *  is opened. The server name will be passed as is to the SysLink
     *  ProcMgr_load() function.
     */
    engDesc.remoteName = "all_DSP.xe674";
 
    /* Add the engine name to the database */
    retVal = Engine_add(&engDesc);
    ...
}

If the engine has a remote server, as in the example above, the server will be queried for its algorithms when the engine is opened. These algorithms will then be added to the engine’s algorithm table. However, these algorithms must be invoked through a stub function on the host (since they are remote), so for the algorithms in the server that you will invoke, you must build the algorithm’s stub function into your application. To enable this, Codec Engine provides an API, Engine_addStubFxns(). For the example above, we would add the following code to bring in the UNIVERSAL stub functions.


    /*
     *  Bring in the UNIVERSAL stub functions so we can create the
     *  universal_copy codec on the remote processor. NOTE: We should
     *  do this before opening the engine.
     */
    Engine_addStubFxns("UNIVERSAL_STUBS", (IALG_Fxns *)&UNIVERSAL_STUBS);

Adding Algorithms to Engines (BIOS 6)

For BIOS 6 based versions of Codec Engine, algorithms can be added at run-time to an engine, using the API, Engine_addAlg(). This API currently only supports adding local algorithms, which can be built into the executable, or reside in a shared library (for Linux OS). If an engine with a remote server executable was added (with Engine_add()) to a SysLink-base system, as in the example in the previous section, there is no need to call Engine_addAlg() for the server algorithms; those algorithms will be added automatically when the engine is opened.

Engine_addAlg() has the following signature:

Engine_Error Engine_addAlg(String name, Engine_Handle engine, String location, Engine_AlgDesc *pAlgDesc);

The engine to which the algorithm is to be added can be specified either by an engine name or an engine handle. If this function is called before any thread has opened the engine, then the ‘name’ parameter must be used to specify the engine, and the ‘engine’ parameter should be set to NULL. In this case, the added algorithm will be available to all threads that subsequently open the engine.

If the engine has already been opened, the ‘engine’ parameter should be used to specify the engine to which the algorithm will be added, and the ‘name’ parameter should be set to NULL. In this case, the algorithm added will only be available for the engine handle that was passed to Engine_addAlg(); attempting to create the algorithm using a different engine handle will fail. Furthermore, if the engine is open, calling Engine_addAlg() with a non-NULL engine name will result in an Engine_EINUSE error. You must use the Engine_Handle parameter to specify the engine, if any thread has opened the engine.

The ‘location’ parameter is a string that can be used to identify the location of the algorithm. For example, this could be the name of a shared library that needs to be loaded. If the algorithm is built into the executable, ‘location’ should be set to NULL.

The ‘pAlgDesc’ parameter holds the algorithm name, functions, codec type, and scratch group ID (if local). For a full description of the Engine_AlgDesc fields, please refer to the Codec Engine API Reference Guide. The fields that you must set depend on whether or not the algorithm is being loaded from a library.

Example 1

The simplest use case of Engine_addAlg() is when the algorithm is built into the executable. The example code below adds a local "universal_copy" algorithm to a "universal_copy" engine. Error checking code has been left out for simplification. For a full listing of this example see the main_hlos_local.c file in the ti/sdo/ce/examples/apps/universal_copy directory.

#include <ti/sdo/ce/Engine.h>
#include <ti/sdo/ce/CERuntime.h>
 
/* Defines the UNIVERSAL_VISATYPE, the type of alg we are adding */
#include <ti/sdo/ce/universal/universal.h>
 
/* Include this header file for algorithm's IALG function table */
#include <ti/xdais/dm/examples/universal_copy/universal_copy_ti.h>
 
Int main(Int argc, String argv[])
{
    Engine_Desc    engDesc;
    Engine_AlgDesc algDesc;
    Engine_Error status;
 
    /* Initialize Codec Engine */
    CERuntime_init();
 
    /* Add the "universal_copy" engine */
    Engine_initDesc(&engDesc);
    engDesc.name = "universal_copy";
    status = Engine_add(&engDesc);
 
    /* Add the "universal_copy" alg to the "universal_copy" engine */
    Engine_initAlgDesc(&algDesc); /* Set fields to defaults */
 
    algDesc.name = "universal_copy";
    algDesc.fxns = (IALG_Fxns *)&UNIVERSALCOPY_TI_IUNIVERSALCOPY;
    algDesc.idmaFxns = NULL;
    algDesc.isLocal = TRUE;
    algDesc.groupId = 0;
    algDesc.iresFxns = NULL;
    algDesc.types = UNIVERSAL_VISATYPE;
 
    status = Engine_addAlg("universal_copy", NULL, NULL, &algDesc);
 
    ...
}

Note that in the code above, we pass NULL for the ‘location’ of the algorithm, and fill in the algorithm descriptor. We also specify the engine name and pass NULL for the handle, since this engine has not been opened.

Example 2

In this example, we assume that the algorithm is built into a Linux shared library called "libsphdec1_copy_dll.so.1.0.0". This shared library must export the following function:

Int GetEngineAlgDesc(Engine_DllAlgDesc *algDesc)

The purpose of GetEngineAlgDesc() is for Codec Engine to query the library for algorithm properties, mainly function tables, and store them in the Engine_DllAlgDesc object. Please refer to the Codec Engine API Reference Guide for a full description of the EngineDllAlgDesc fields.

Below is a partial listing of the code. The libraries for this example are located in ti/sdo/ce/examples/codecs/sphdec1_copy_dll and sphenc1_copy_dll, and the application is located in ti/sdo/ce/examples/apps/speech1_copy_dll.

Int smain(Int argc, String argv[])
{
    Engine_Desc    desc;
    Engine_AlgDesc attrs;
    Engine_Error status;
 
    ...
 
    /* Add the engine name to the set of engines that can be opened */
    Engine_initDesc(&desc);
    desc.name = "engine";
    status = Engine_add(&desc);
 
    /* Open the engine */
    ce = Engine_open("engine", NULL, NULL);
 
    /*
     *  Fill in the alg descriptor. Note: We only need to fill in the
     *  name and groupId since alg functions will be obtained from the
     *  library.
     */
    Engine_initAlgDesc(&attrs);
 
    attrs.name = "sphdec1_copy";
    attrs.groupId = 1;
 
    /* Add the alg. Pass engine handle, since engine is opened. */
    status = Engine_addAlg(NULL, ce, "./libsphdec1_copy_dll.so.1.0.0",
                           &attrs);
 
    ...
}

Getting Memory and CPU Information from an Engine

You can use the Engine_getUsedMem() function to get information about memory used by an Engine instance. The value returned is the total amount of memory (in MAUs) currently allocated from the available heaps. Note that this value may vary between calls, depending upon DSP Server activity. For example, when the first algorithm is instantiated on the DSP Server, data structures in addition to those needed for the individual algorithm instance may be allocated. This extra memory is allocated "when first needed" and remains allocated with its global state retained even after this algorithm is deleted. The memory is not reallocated on subsequent allocations of this or other algorithms on the DSP Server.

In addition to Engine_getUsedMem(), there are Server APIs for getting information about the memory usage of individual heaps on the DSP.

You can use the Server_getCpuLoad() function to get the DSP Server's CPU usage as an integer from 0 to 100. This value indicates the percentage of time the DSP Server is processing measured over a period of approximately 1 second. If the load is unavailable, a negative value is returned.

Getting Information about Algorithms Configured into an Engine

An application may determine the number of algorithms configured into an Engine and their properties, such as the name of the algorithm and whether it is local or remote.

The number of algorithms can be obtained with the following API:

Engine_Error Engine_getNumAlgs(String name, Int *numAlgs);

The parameter, "name", is the name of the Engine. This function returns the following values:

  • Engine_EOK - Success. In this case, *numAlgs returns the number of algorithms configured into the Engine.
  • Engine_EEXIST - There is no Engine with the given name.


Newer versions of Codec Engine have added the API, Engine_getNumAlgs2():

Engine_Error Engine_getNumAlgs2(String name, Engine_Handle engine, Int *numAlgs);

This allows you to specify the engine by either a handle, if the engine has been opened, or by name. By using the engine handle, the number of algorithms returned will include those added after the engine was opened. Use either "name" or "engine", and set the other to NULL.

Once the number of algorithms in the Engine is known, the application can iteratively call the function Engine_getAlgInfo() to obtain information about each of the algorithms. The information is put into the Engine_AlgInfo structure, which is defined as follows:

typedef struct Engine_AlgInfo {
    Int     algInfoSize;    /* Size of this structure */
    String  name;           /* Name of algorithm */
    String *typeTab;        /* inheritance hierarchy */
    Bool    isLocal;        /* if TRUE, run locally */
} Engine_AlgInfo;

The first field of the Engine_AlgInfo structure, .algInfoSize, must be set by the application to the size of this structure; it will be used to support future enhancements to this structure. The following example shows the usage of these APIs (error checking has been left out for readability):

Int numAlgs, i;
Engine_AlgInfo algInfo;
 
/* Determine how many algorithms are in the "audio_copy" Engine */
Engine_getNumAlgs("audio_copy", &numAlgs);
 
/* just need to initialize this struct once, so outside of the for loop below */
algInfo.algInfoSize = sizeof(Engine_AlgInfo);
 
/* for each algorithm in the "audio_copy" Engine, print its details */
for (i = 0; i < numAlgs; i++) {
    Engine_getAlgInfo("audio_copy", &algInfo, i);
    printf("alg[%d]: name = %s typeTab = %s local = %d\n", i, algInfo.name, *(algInfo.typeTab), algInfo.isLocal);
}

The output may look like the following:

alg[0]: name = auddec_copy typeTab = ti.sdo.ce.audio.IAUDDEC local = 0
alg[1]: name = audenc_copy typeTab = ti.sdo.ce.audio.IAUDENC local = 0

The typeTab field indicates what 'type' of algorithm it is (e.g. video decoder, image encoder, etc.). This field is actually a NULL-terminated array of strings giving the inheritance hierarchy; in the example above, we printed out the first string only. This field can be used, for example to tell the difference between an XDM 0.9 audio decoder (i.e. ti.sdo.ce.audio.IAUDDEC) and a XDM 1.0 audio decoder (i.e. ti.sdo.ce.audio1.IAUDDEC1).

The return values of Engine_getAlgInfo() are the following:

  • Engine_EOK - Success.
  • Engine_EEXIST - There is no Engine with the given name.
  • Engine_EINVAL - The .algInfoSize field of the Engine_AlgInfo object passed to this function does not match the size of the Engine_AlgInfo object in the Codec Engine library.
  • Engine_ENOTFOUND - The index of the algorithm is out of range.


Newer versions of Codec Engine have added the following API:

Engine_Error Engine_getAlgInfo2(String name, Engine_Handle engine,
        Engine_AlgInfo2 *algInfo2, Int index);

This API can be passed either an engine handle or an engine name, and the one that is not used should be set to NULL. The Engine_AlgInfo2 structure is defined as:

typedef struct Engine_AlgInfo2 {
    Int         algInfoSize;    /**< Size of this structure. */
    String      name;           /**< Name of algorithm. */
    String      types;          /**< Inheritance hierarchy. */
    Bool        isLocal;        /**< If TRUE, run locally. */
} Engine_AlgInfo2;

Note that the only change from Engine_AlgInfo to Engine_AlgInfo2 is the "types" string that replaces the "typeTab" string array. The "types" string is a ‘;’ separated string of the codec class inheritance hierarchy of the algorithm.

The VISA Classes: Video, Image, Speech, Audio... and more!

For each of the VISA classes (e.g. VIDDEC, AUDENC, UNIVERSAL, etc.), the following APIs are provided, where MOD is the module prefix:

  • MOD_create() - Create an instance of this type of algorithm.
  • MOD_process() - Execute the "process" method in this instance of the algorithm. Block until complete.
  • MOD_control() - Execute the "control" method in this instance of the algorithm.
  • MOD_delete() - Delete the specified instance of this type of algorithm.

In the CE 2.10 release, asynchronous APIs were added (for remote algorithms only) to enable applications to return immediately from the MOD_process() calls. Due to that support, each module also includes:

  • MOD_processAsync() - Begin executing the "process" method in this instance of the algorithm. Return immediately.
  • MOD_processWait() - Wait for the "process" method in a previously called MOD_processAsync call to complete.

VISA API Setup Code

For each VISA API module your application uses, you should include the appropriate header file. For example, the following statement includes the header file for the AUDDEC audio decoder API module. The directory path is relative to the CE_INSTALL_DIR/packages package repository.

#include <ti/sdo/ce/audio/auddec.h>

Creating an Algorithm Instance

To create an algorithm instance within an Engine, you use the *_create() function for the appropriate VISA encoder or decoder module. For example:

Engine_Handle hEngine;
AUDDEC_Handle hAuddec;
 
/* allocate and initialize audio decoder on the Engine */
hAuddec = AUDDEC_create(hEngine, "auddec_copy", NULL);

In this function, the first argument — hEngine — is the Engine_Handle returned by the Engine_open() function.

The second argument — "auddec_copy" - is a string that identifies the type of algorithm to create. These strings are part of the configuration created by your Engine Integrator.

The third argument allows you to specify parameters to use when instantiating the algorithm. These parameters control aspects of algorithm behavior. The parameter structure is different for each VISA encoder or decoder class. For example, the audio decoder parameter structure is as follows:

typedef struct IAUDDEC_Params {
    XDAS_Int32 size;           /* Size of this structure */
    XDAS_Int32 maxSampleRate;  /* Max sampling freq in Hz */
    XDAS_Int32 maxBitrate;  /* Max bit-rate in bits per sec */
    XDAS_Int32 maxNoOfCh;  /* Max number of channels */
    XDAS_Int32 dataEndianness; /* Endianness of input data */
} IAUDDEC_Params;

This function returns a handle that other functions use to access the algorithm instance.

Deleting an Algorithm Instance

To delete an algorithm instance and free the resources it uses, your application calls MOD_delete(). For example:

/* tear down the codec and Engine */
AUDDEC_delete(hAuddec);

You should do this only after you have freed any buffers or other memory related to the algorithm instance.

Controlling an Algorithm Instance

You can control and query the capabilities of an algorithm using the module's MOD_control() function.

For example, the following code uses the AUDDEC_control() function to query a decoder to verify that the decoder accepts one input buffer, returns one output buffer, and uses buffer sizes that can handle NSAMPLES bytes of data.

#define NSAMPLES  1024
#define IFRAMESIZE (NSAMPLES * sizeof(Int8)) /* raw (in) */
#define OFRAMESIZE (NSAMPLES * sizeof(Int8)) /* decoded */
 
static Char inBuf[IFRAMESIZE];
static Char outBuf[OFRAMESIZE];
 
XDM_BufDesc inBufDesc;
XDM_BufDesc outBufDesc;
XDAS_Int32 status;
XDAS_Int32 bufSizes = NSAMPLES;
IAUDDEC_DynamicParams decDynParams;
IAUDDEC_Status decStatus;
 
/* prepare "global" buffer descriptor settings */
inBufDesc.numBufs = outBufDesc.numBufs = 1;
inBufDesc.bufSizes = outBufDesc.bufSizes = &bufSizes;
 
/* Query the decoder */
status = AUDDEC_control(hAuddec, XDM_GETSTATUS, &decDynParams, &decStatus);
if (status != AUDDEC_EOK) {
    /* failure, report error and exit */
    printf("decode control status = %ld\n", status);
    return;
}
 
/* Validate decoder codec will meet buffer requirements */
if ((inBufDesc.numBufs > decStatus.bufInfo.maxNumInBufs) ||
    (sizeof(inBuf) > decStatus.bufInfo.maxInBufSize[0]) ||
    (outBufDesc.numBufs > decStatus.bufInfo.maxNumOutBufs) ||
    (sizeof(outBuf) > decStatus.bufInfo.maxOutBufSize[0])) {
 
    /* failure, report error and exit */
    printf("Error:  decoder codec feature conflict\n");
    return;
}

In the AUDDEC_control() function example, the first argument hAuddec is the handle to the algorithm returned by the AUDDEC_create() function.

The second argument is a command ID constant from ti/xdais/dm/xdm.h. More details about available command IDs is available in the XDM Reference Guide's XDM_CmdId documentation

The third and fourth arguments are the addresses of MOD_DynamicParams and MOD_Status structures, respectively. These structures are different for each of the VISA APIs.

Processing Data with an Algorithm Instance

You can run an algorithm using the module's MOD_process() function.

For example, the following code continues the example in the previous section. It uses the AUDDEC_process() function to read frames from "in", decode the audio, and write the output to "out".

Int n;
XDM_BufDesc           inBufDesc;
XDM_BufDesc           outBufDesc;
IAUDDEC_InArgs        decInArgs;
IAUDDEC_OutArgs       decOutArgs;
 
/* prepare "global" buffer descriptor settings */
inBufDesc.numBufs = outBufDesc.numBufs = 1;
inBufDesc.bufSizes = outBufDesc.bufSizes = &bufSizes;
decInArgs.size = sizeof(decInArgs);
...
 
/* Read complete frames from in, decode and write to out */
for (n = 0; fread(inBuf, sizeof (inBuf), 1, in) == 1; n++) {
    XDAS_Int8 *src = inBuf;
    XDAS_Int8 *dst = outBuf;
 
    /* prepare "per loop" buffer descriptor settings */
    inBufDesc.bufs = &src;
    outBufDesc.bufs = &dst;
    decInArgs.size = sizeof(decInArgs);
    decInArgs.numBytes = sizeof(inBuf);
 
    /* decode the frame */
    status = AUDDEC_process(hAuddec, &inBufDesc, &outBufDesc,
        &decInArgs, &decOutArgs);
 
    if (status != AUDDEC_EOK) {
        printf("frame %d: decode status = %ld\n", n, status);
    }
 
    /* write to file */
    fwrite(dst, sizeof (outBuf), 1, out);
}
printf("%d frames decoded\n", n);

In this AUDDEC_process() function example, the first argument — hAuddec — is the handle to the algorithm returned by the AUDDEC_create() function.

The second and third arguments for the audio decoder module (and for most other VISA modules) provide the address of a buffer descriptor structure of type XDM_BufDesc. This type has the following structure definition:

typedef struct XDM_BufDesc {
    XDAS_Int8  **bufs;
    XDAS_Int32  numBufs;
    XDAS_Int32  *bufSizes;
} XDM_BufDesc;

The fourth and fifth arguments for the AUDDEC module (and for most other VISA modules) provide the address of input and output arguments for the algorithm. This structure is different for each of the VISA classes.

Overriding a Remote Algorithm's Priority and Memory Requests

Overriding the Algorithm's Configured Priority

In some situations, an application developer may want to run multiple instances of a remote codec at different priorities. As an example, suppose you want to run two instances of the sample audio encoder copy codec: one at priority 4, the other at priority 5. The server containing this codec is originally configured with the audio encoder running at priority 4, as shown in the following configuration code (assuming that Server.MINPRI is 1):

Server.algs = [
    {name: "audenc_copy", mod: AUDENC_COPY, threadAttrs: {
        stackMemId: 0, priority: Server.MINPRI + 3}
    },
    ...
];

It may seem that the solution to this problem is to configure the DSP Server by adding another audio encoder with the new priority and a different name to the list of server algorithms, as follows:

Server.algs = [
    /* Audio copy encoder configured with priority 4 */
    {name: "audenc_copy", mod: AUDENC_COPY, threadAttrs: {
       stackMemId: 0, priority: Server.MINPRI + 3}
    },
 
    /* Audio copy encoder configured with priority 5 ('''WRONG''')*/
    {name: "audenc_copy_2", mod: AUDENC_COPY, threadAttrs: {
       stackMemId: 0, priority: Server.MINPRI + 4}
    },
    ...
];

However, this generates an error when trying to rebuild the server, since the auto-generated UUIDs for these two codecs, determined by the mod (AUDENC_COPY) configuration parameter, will be identical. Since it is the UUID, and not the codec name, that is passed internally from the ARM application to the DSP server to instantiate the codec, these two codecs would be indistinguishable. Therefore, this method will not work.

The correct way to create a codec with a priority other than the one configured in the DSP server, is through the name parameter passed to the MOD_create() API. The name will be the codec name with the overriding priority appended to it, separated with a ":". For example, to run the audio encoder shown above at priority 5, pass the name "audenc_copy:5" to AUDENC_create(). The following code fragment creates two audio copy encoders running at different priorities (error checking is left out for readability).

Engine_Handle  hEngine;
AUDENC_Handle  enc;
AUDENC_Handle  enc_high;
 
hEngine = Engine_open("audio_copy", NULL, NULL);
 
/* Create codec at the server-configured priority */
enc = AUDENC_create(hEngine, "audenc_copy", NULL);
 
/*
 * Create second instance of codec, overriding the
 * server-configured priority with a priority of 5.
 */
enc_high = AUDENC_create(hEngine, "audenc_copy:5", NULL);

Overriding an Algorithm's Memory Requests

It is possible to ignore a codec's requests for placement of allocated buffers and force all of the codec's memory requests to be allocated in the external heap mapped to the DSKT2 module's ESDATA configuration parameter.

This is also done by appending to the codec name passed to the MOD_create() function. To override memory placement requests, append ":1" to the name after the adjustment for priority. For example, the names below passed to AUDENC_create() have the following meanings:

  • "audenc_copy:5:1"
Create audenc_copy with priority 5 and with buffers allocated in external memory.
  • "audenc_copy::1"
Create audenc_copy with its configured priority and with buffers allocated in external memory.

Appending "::0" to the codec name (or ":0" if a new priority is also appended), means the codec memory requests should be respected. For example, passing the following names to AUDENC_create() are equivalent:

  • "audenc_copy"
  • "audenc_copy::0"

The Server APIs

On multicore systems (both heterogeneous and homogenous), Engines that are configured with remote algorithms (e.g., algorithms on other processors) transparently use a "Server". The Server is an executable that includes algorithms and their frameworks (e.g., BIOS, Framework Components, codecs, etc).

For heterogeneous systems where Link is used, the Server typically is dynamically loaded onto the remote processor during Engine_open().

The Server APIs can be used by applications to access information about the remote Server and to control the Server. More specifically, these APIs allow an application to obtain information about the number of memory heaps configured in the Server, the current usage of an individual memory heap, and to reconfigure the base and size of the Server's algorithm heap.

The APIs related to the Server are:

  • Engine_getServer() - Get the handle to a Server.
  • Server_getNumMemSegs() - Get the number of heaps in a Server.
  • Server_getMemStat() - Get statistics about a Server heap.
  • Server_redefineHeap() - Set base and size of a Server heap.
  • Server_restoreHeap() - Reset Server heap to default base and size.

Getting a Server Handle

To access the Server for the Engine, the application must first obtain a Server handle by calling the Engine_getServer() API. For example:

Engine_Handle hEngine;
Server_Handle hServer;
 
hEngine = Engine_open("auddec", NULL, NULL);
hServer = Engine_getServer(hEngine);

Note: As with Engine handles, Server handles are not thread protected. Each thread that uses a Server handle must perform its own Engine_getServer() call (using its own Engine handle) or guarantee synchronized access to a shared Server handle. If the value returned by Engine_getServer() is NULL, then the Engine has no Server.


Getting Memory Heap Information

The application can obtain the number of memory heaps configured into the Server by calling Server_getNumMemSegs(). For example:

Server_Handle hServer;
Int numSegs;
 
/* Get the server handle from a previously opened Engine */
hServer = Engine_getServer(hEngine);
 
/* Determine how many memory segments exist on the remote Server */
Server_getNumMemSegs(hServer, &numSegs);

This API returns the following error codes:

  • Server_EOK - success. In this case, numSegs contains the number of heaps in the Server.
  • Server_ERUNTIME - an internal runtime error occurred.

Once the number of heaps is known, the application can then iterate through this number, to obtain statistics about each heap, using Server_getMemStat(). The memory statistics are returned in a Server_MemStat structure:

typedef struct Server_MemStat {
    Char   name[Server_MAXSEGNAMELENTH+1];  /* Name of heap segment */
    Uint32 base;                            /* Base address of heap */
    Uint32 size;                            /* Original heap size */
    Uint32 used;                            /* DSP MAUs of heap used */
    Uint32 maxBlockLen;                     /* Length of largest free block */
} Server_MemStat;

The following example code shows the usage of these APIs (error checking is left out for readability).

Server_Handle hServer;
Int i, numSegs;
Server_MemStat stat;
 
/* Determine how many memory segments exist on the remote Server */
Server_getNumMemSegs(hServer, &numSegs);
 
/* For each memory segment, print details about it */
for (i = 0; i < numSegs; i++) {
    Server_getMemStat(hServer, i, &stat);
    printf("%s: base: 0x%x size: 0x%x used: 0x%x, max free block: 0x%x",
            stat.name, stat.base, stat.size, stat.used, stat.maxBlockLen);
}

The values returned by Server_getMemStat() are the following:

  • Server_EOK - Success
  • Server_ENOTFOUND - The segment number was out of range
  • Server_ERUNTIME - An internal runtime error occurred

Reconfiguring the Server's Algorithm Heap

Note, this is an advanced feature that most users will not utilize.

The Server is configured with a memory segment that is used exclusively for algorithm heaps - DDRALGHEAP. In some situations, the Server may intentionally be configured with a minimal algorithm heap, with the intent that the application may want to provide, at runtime, a larger contiguous memory block to be used by the Server for the algorithm heap. This enables the algorithm heap to be allocated at runtime (rather than reserved at build time), and allows the app processor to use this memory for other purposes when the Server is not being used.

The following Server APIs provide the means to reconfigure the algorithm heap:

Server_Status Server_redefineHeap(Server_Handle server, String name, Uint32 base, Uint32 size);
 
Server_Status Server_restoreHeap(Server_Handle server, String name);

The name parameter passed to these functions is the name of the heap to be reconfigured; it must not be more than Server_MAXSEGNAMELENGTH characters long. The base address passed to Server_redefineHeap() must be a Server-appropriate address, and the memory from base to base + size must be physically contiguous. The size parameter is given in Server-specific MADUs (minimum addressable data units). The base address should be 8-byte aligned, but there are no alignment restrictions on size; a value of 0 for size is acceptable.

The Server's algorithm heap can only be reconfigured when no memory is currently allocated in the heap. The Server_restoreHeap() function resets the base address and size of the algorithm heap back to their original values (the values before any calls to Server_redefineHeap() were made). After a successful call to Server_restoreHeap(), the memory previously "redefined" to the heap can be used again by the system.

The return values of Server_redefineHeap() are the following:

  • Server_EOK - Success.
  • Server_EINVAL - Changing to the new base address and size would cause an overlap with another heap.
  • Server_EINUSE - Memory is currently allocated in the algorithm heap.
  • Server_ENOTFOUND - No heap with the given name was found.
  • Server_ERUNTIME - An internal runtime error occurred.

Server_restoreHeap() returns any of the following values:

  • Server_EOK - Success.
  • Server_EINVAL - Changing back to the original base address and size would cause an overlap with another heap.
  • Server_EINUSE - Memory is currently allocated in the algorithm heap.
  • Server_ENOTFOUND - No heap with the given name was found.
  • Server_ERUNTIME - An internal runtime error occurred.

The following code illustrates how these two APIs could be used on a GPP+DSP device. In this example, a physically contiguous chunk of memory is allocated by the GPP application using Memory_contigAlloc(). However, the address returned by this function is a virtual address on the GPP, so it must be converted to a DSP address before passing it to Server_redefineHeap(). The Memory_getBufferPhysicalAddress() function converts the virtual address to a physical address on the GPP.

After the algorithm is run, the algorithm heap is reset to its original size and location. Error checking is left out for better readability.

Engine_Handle hEngine;
Server_Handle hServer;
XDAS_Int8  *buf;
Uint32 physAddr;
 
/*
 * Open the Engine and get Server handle. Note, the
 * Engine_open() call will load and start the DSP.
 */
hEngine = Engine_open("audio_copy", NULL, NULL);
hServer = Engine_getServer(hEngine);
 
/* Allocate a large block of physically contiguous memory for our "alg heap" */
buf = (XDAS_Int8 *)Memory_contigAlloc(BUFSIZE, ALIGNMENT);
 
/* Convert virtual address to physical address. */
physAddr = Memory_getBufferPhysicalAddress(buf, BUFSIZE, NULL);
 
/* Reconfigure the algorithm heap */
Server_redefineHeap(hServer, "DDRALGHEAP", physAddr, BUFSIZE);
 
/* ...Create, run and delete codecs... */
 
/* Reconfigure alg heap back to its original state. */
Server_restoreHeap(hServer, "DDRALGHEAP");
 
/* Free the "alg heap" buffer */
Memory_contigFree(buf, BUFSIZE);

In other scenarios, the application may need to reconfigure the algorithm heap to an address that is not obtained by allocating a buffer on the ARM. For example, suppose there are fixed memory spaces on the DSP that the application will alternate between for the algorithm heap, depending on what algorithms will be run. In this case, the application can pass the DSP address directly to Server_redefineHeap().

Getting Trace from the Server

The Server API, Server_setTrace(), provides a way to set trace masks on the remote server from the host application at run-time. This function takes a server handle and trace mask string as arguments:

    Int Server_setTrace(Server_Handle server, String mask)

The format of the mask string depends on whether you are using a BIOS 5 or a BIOS 6 based version of Codec Engine. Please see the appropriate section of, “What about Software Trace?” for the description of trace mask strings that apply.

Server_fwriteTrace() can be used to write the server’s trace buffer to a file stream. The API is the following:

    Int Server_fwriteTrace(Server_Handle server, String prefix, FILE *out)

The ‘prefix’ string will be pre-pended to each output line for easy identification. This function returns the number of bytes written to the file stream, including the ‘prefix’ string. If you do not want a prefix to be pre-pended to the trace output, just use “” for the ‘prefix’ string (not NULL).

In multi-process systems where the Link Arbiter Daemon (LAD) is used to channel requests to the server, Server_connectTrace() should be called before calling the Server trace APIs, and Server_disconnectTrace() should be called after (see example below).

Below are two examples of Server_setTrace() and Server_fwriteTrace() usage, the first for BIOS 5 versions of Codec Engine, and the second for BIOS 6 versions. The only difference between the two use cases are the masks passed to Server_setTrace().

Example BIOS 5

In this example, it is assumed that LAD is used to send requests to the server. Error checking has been omitted from the code fragment below, but the source code in the Codec Engine example app, server_api_example, can be consulted for proper error handling. Here we turn on trace levels 0, 5, 6, and 7 for all Codec Engine modules. The level 0 is used mainly for function entry and exit, levels 5, 6, and 7 correspond to benchmark trace, warnings, and errors, respectively.

    /* Connect for server trace data */
    status = Server_connectTrace(server, &traceToken);
 
    /* Set server trace mask */
    status = Server_setTrace(server, "*=0567");
 
    /* Dump server trace to stdout */
    count = Server_fwriteTrace((Server_Handle)server, "", stdout);
 
    /* discconnect from server trace data */
    status = Server_disconnectTrace(server, traceToken);

Example BIOS 6

In this example, we assume LAD is not used, and as in the previous example, we will turn on function entry and exit tracing, and levels 5, 6, and 7 of Codec Engine modules. See “Trace Mask Values” for details on the different trace level values.

    /* Set server trace mask */
    status = Server_setTrace(server, "ti.sdo.ce.%=EX567");
 
    /* Dump server trace to stdout */
    count = Server_fwriteTrace((Server_Handle)server, "", stdout);

Getting Server CPU Load

Applications running on the host can call Server_getCpuLoad() to get an estimate of the CPU load on the remote server. The CPU load is averaged over the benchmark window configured for the ti.sysbios.utils.Load module. Below is an example of Server_getCpuLoad():

Engine_Handle   engine = NULL;
Server_Handle   server = NULL;
Int             load = 0;
 
/* initialize Codec Engine runtime first */
CERuntime_init();
 
engine = Engine_open(engineName, NULL, NULL);
server = Engine_getServer(engine);
 
load = Server_getCpuLoad(server);

Note that Server_getCpuLoad() simply leverages the BIOS Load feature. In BIOS 5, this was provided via the BIOS Utils product. The Load feature was subsequently integrated into, and redistributed with, BIOS 6.

Example BIOS 5

Prior to CE 1.20, users had to explicitly add the following to their server .cfg scripts to enable CPU load gathering:

var Load = xdc.useModule('ti.bios.utils.Load');

In addition, they had to call Load_init() after CERuntime_init() in their main() fxn, as follows:

#include <ti/bios/utils/load/Load.h>
 
Void main(Int argc, Char *argv[])
{
    /* init Codec Engine */
    CERuntime_init();
 
    Load_init();
    ...
}

In CE 1.20 and later, this is taken care of internally by CERuntime_init(), and neither of these steps (.cfg script nor Load_init()) are required.

Example BIOS 6

The following code can be added to a BIOS 6 server to enable gathering of CPU load data by the Host application.

var Load = xdc.useModule('ti.sysbios.utils.Load');
 
Load.hwiEnabled = false;
Load.swiEnabled = false;
Load.taskEnabled = true;
Load.autoAddTasks = false;
Load.common$.diags_USER4 = Diags.ALWAYS_OFF;

What Happens to DSP Memory Issues?

The VISA APIs to create and delete algorithms provided by the Codec Engine manage all algorithm resources. This includes the CPU, memory, direct memory access (DMA), and more. The VISA creation and deletion APIs hide most of the details of the codecs' memory and resource management.

Buffer Handling and Shared Memory Maps

It is the responsibility of the application to handle all I/O and buffering issues. The VISA APIs use various buffer descriptors (e.g. XDM_BufDesc, XDM_BufDesc1, IVIDEO_BufDesc, etc) to manage data buffers.

In shared memory-based systems (e.g. OMAP3, DM644x, OMAP-L13x, etc), you must use memory that both the app and server can access (e.g. CMEM). This enables efficient data passing between cores as only pointers are passed and no copies are made. In addition, these shared memory-based buffers must be physically contiguous and cache-aligned.

For example, the DM644x default memory map is designed with the intent to provide generous amount of space for DSP code and data, plenty of private heap for DSP algorithms, and a large space for shared buffers between GPP and DSP.

Table 4–3 DM644x Default Memory Map
Address (hex) Address (decimal) Size Segment Comments
0x80000000 ..

0x87800000

0-120MB 120MB Linux booted with MEM=120M
0x87800000 ..

0x88000000

120-128MB 8MB CMEM shared buffers between GPP and DSP
0x88000000 ..

0x8FA00000

128-250MB 122MB DDRALGHEAP * DSP segment used exclusively for algorithm heaps
0x8FA00000 ..

0x8FE00000

250-254MB 4MB DDR * DSP segment for code, stack, and static data
0x8FE00000 ..

0x8FF00000

254-255MB 1MB DSPLINKMEM * memory for DSPLINK
0x8FF00000 ..

0x8FF00080

255MB-255MB 128B CTRLRESET * memory for reset vectors
0x8FF00080 ..

0x8FFFFFFF

255MB-256MB 1MB -- unnamed --

(*) are actual DSP linker segments.

Memory Fragmentation

For dual CPU applications, the exception to the rule that the Codec Engine hides DSP memory management issues from the GPP application developer is that buffers passed to the DSP must be contiguous in physical memory and cache-aligned.

This differs from buffer handling on the GPP because Linux and similar GPP operating systems handle non-contiguous buffers through a memory management unit (MMU) that holds a table matching virtual addresses to physical addresses. Many DSPs have no such table. (Note that the OMAP35x devices do have a DSP-side MMU, but Codec Engine still requires that buffers passed from the GPP to a remote DSP be physically contiguous.)

The Codec Engine verifies that these constraints are met in the required platform for data buffers. Algorithm buffers are managed by the Memory_ module, which uses pools of different sizes to ensure that memory is not fragmented.

However, the storage space for codec instances created by the Codec Engine must also be contiguous and cache-aligned. The creation and deletion of codec instances is non-deterministic. For example, if your application follows steps like those in the figure below, it may be impossible to recreate a codec instance that was created and deleted earlier:

buffer creation and deletion
Figure 1. Non-Deterministic Buffer Creation and Deletion

Since instance creation occurs "in the background" while other codecs are running at higher priorities, you cannot guarantee the time required to create an instance. You can, however, control the order in which instances are created and deleted.

If a codec or shared buffer is not physically contiguous, when the caller calls Memory_getBufferPhysicalAddress() with a non-NULL pointer for the Boolean *isContiguous arguments, the Codec Engine sets the ptr data to true or false without printing any message. If the pointer is NULL (which is most likely, since this function is called by the codec stubs, which pass NULL for this ptr), the level 7 trace message is:

Memory_getBufferPhysicalAddress> ERROR: user buffer at addr=<hex addr>, size=<size in bytes> is NOT contiguous
This message is printed only if level 7 tracing is enabled, which is the default.

Cache Alignment

Devices that utilize a cache (for example, the C64+) also require that I/O buffers be cache-aligned. For example, the DSP L2 cache line size on the C64x+ is 128 bytes. Storage space allocated must start at a cache line boundary, and the size must be a multiple of the cache line length. If these alignment and size constraints are violated, any data object allocated adjacent to the application buffer will share a cache line with a portion of the application buffer. This line may be corrupted as a result of the Cache Controller writing back the shared cache line.

Cache Coherence

When developing an application for a multiprocessor platform (including those with multiple processing cores, hardware accelerators, and DMA Engines) in which some memory regions are cached, you must perform some memory coherence operations. The Codec Engine framework, when it has enough information, automatically handles some cache coherence operations. However, ultimately, the application developer is responsible for ensuring that certain pre-and post-conditions are met for the buffers the application submits and receives from the Codec Engine. The subsections that follow summarize the responsibilities of the application developer for different processor environments.

GPP + DSP Environments

The following are common issues in the DaVinci environment, though they are present in any multi-core system that utilizes a cache. Note that the Codec Engine Framework enforces some of these rules in the implementation of the VISA APIs. You should be aware of these rules when accessing shared memory outside the use of the VISA APIs.

  • Input Buffers. This is the case when a GPP application captures/generates a buffer and passes it to the DSP.
    • GPP side. Input buffers must be written-back before each process/control call. (Otherwise, DSP CPU/DMA accesses will access incoherent data in external memory, with no ability to writeback the GPP-side cache.) If a buffer is not cached on the GPP side, a writeback is not required. When a driver fills a GPP-cached input buffer, before passing it to the Codec Engine, the driver should do the following: 1) Start with a cache invalidated buffer. 2) The driver can use DMA or CPU writes to fill the buffer. 3) If CPU is used to fill the buffer, it must be written-back before passing the buffer to Codec Engine.
    • DSP side. Input buffers must be invalidated before each process/control call. (Otherwise the DSP may read stale data from its cache. This is possible if the same buffer was passed in an earlier call.) Note that the default skeletons for the VISA APIs automatically invalidate input buffers prior to invoking the algorithm's process function.
  • Output Buffers.
    • GPP side. Output buffers must be invalidated before accessing them on the GPP side following DSP-side processing. (Otherwise the GPP may access stale data resident in its GPP-side cache.) If the buffers are not cache-enabled on the GPP side invalidation is not required.
    • DSP side. Output buffers must be invalidated before each process/control call. (Otherwise, if DMA is used to fill the buffer, there may be overwrites as cache lines are evicted due to unrelated CPU activity.) Also, output buffers must be written-back after each process/control call. (Otherwise the GPP may read incoherent data from external memory.) Note that the default skeletons for the VISA APIs automatically writeback output buffers following each process/control call.
    • DMA-Related. If the GPP or DSP uses DMA to access shared buffers, there is more work to ensure coherence. XDAIS provides some DMA rules for frameworks. See TMS320 DSP Algorithm Standard Rules and Guidelines (SPRU352) for details.

C6000 algorithms must not issue any CPU read/writes to buffers in external memory that are involved in DMA transfers. This also applies to the input buffers passed to the algorithm through its algorithm interface. Some common cache-related errors are:

  • Doing a cache writeback-invalidate for DSP "input" buffers, instead of just an invalidate "before" a process/control call. In this case, if any of the "current" input buffers has been referenced in a "previous" process/control call, then a stale fragment of that buffer may already be resident in the DSP cache. A writeback will corrupt the "current" input buffer with stale data from the cache.
  • Doing a blind "ALL L2 Cache" writeback-invalidate, instead of a writeback or invalidate on only the algorithm's own input/output buffers. This creates potential problems for other algorithm instances, whose input/output buffers will be affected.
  • Invalidating all of L2 severely degrades performance for all algorithm instances, due to the resulting cache misses.

Single-Processor Environments

The following are common issues in the DM643x environment, though they are present in any single-CPU system that utilizes cached memory.

  • Input Buffers. This is the case when a DSP application captures/generates a buffer and passes it to the Codec Engine. Depending upon how the input buffers have been captured, the buffers must be either invalidated or written-back and invalidated:
    • If the application (or a driver) modified the contents of the input buffer using CPU read and/or write operations, the buffer must be written-back and invalidated.
    • If the application (or driver) modified the contents of the input buffer using DMA, then the buffer must not be written-back, but must still be invalidated.
In both cases, the application (or driver) should start filling a cache-invalidated buffer.
  • Output Buffers. Output buffers (those filled by Codec Engine) must be invalidated before being submitted to the Codec Engine to be filled. And, when returned from the Codec Engine, the buffers should be written-back to ensure all data is written out to external memory.

What Happens to DSP Real-Time Issues?

It is the responsibility of the GPP application to handle all multi-threading and real-time issues from a GPP perspective. For example, this may involve scheduling higher-frequency, short-duration processing (such as audio) at a higher priority than long-duration processing (such as video) on Linux-based systems. The DSP Server used transparently by the Codec Engine for remote algorithms handles multi-threading and reentrancy issues on the DSP. For platforms such as the DM644x that treat the DSP as a black box, threading issues on the DSP are managed by the Codec Server integration. However, there are still some important considerations.

Transaction Latency

It is important to consider transaction latency when running DSP algorithms from the GPP. For example, on the DM644x, the round-trip time required to schedule a DSP algorithm from the GPP limits transactions-per-second to approximately 7000. That is, the application can use the Codec Engine to run or control an algorithm up to 7000 times per second.

This may seem like plenty of headroom when considering typical frame rates of 30 to 50 per second. However, be aware that applications with a high density of channels may run up against this limit.

Multi- vs. Uni-Processor Performance

The VISA APIs wait for the function to return. Thus, your application needs to be multi-threaded if you want other threads to use the processing time while waiting for the DSP to perform its algorithms.

A discussion of managing multiple GPP threads in conjunction with the Codec Engine is beyond the scope of this document. See the documentation for your GPP operating system and/or middleware.

Local Performance

The Codec Engine is also optimized for local algorithm execution. The execution overhead is the same as that of XDAIS algorithms. The creation times are slightly higher.

What About Codec Engine Debugging?

Codec Engine modules, both on the application and the server side, provide plenty of trace information that can be activated, to reveal what's happening internally.

When any object in your application fails to be created—a codec or an Engine, either locally or on the DSP—follow the instructions in this section to turn on Codec Engine trace in order to do basic debugging. The section titled What About Software Trace? provides details about Codec Engine tracing, although it is generally needed when debugging real-time, performance issues.

Codec Engine Debugging from the ARM on ARM+DSP Systems

See the CE_DEBUG article for details on easily enabling the detailed trace embedded throughout Codec Engine.

Codec Engine Debugging on a DSP-only System

On a BIOS-based system, assuming you are debugging your application from Code Composer Studio, you turn tracing on from your C code. (This is primarily because CCS doesn't support setting environment variables on the target processor, making the CE_DEBUG technique of enabling trace impossible.)

To do so, when you are ready to show Codec Engine trace information — which can be as soon as right after a call to CERuntime_init() — add the following lines to your code (assuming you have done #include <stdio.h> and #include <ti/sdo/ce/trace/gt.h>):

/* print trace using printf() (which sends it to CCS's stdio console window) */
GT_setprintf((GT_PrintFxn)printf);
 
/* for all modules (*), enable trace (+) for warnings (6) and errors (7) */
GT_set("*+67");

The GT_set() call configures how much tracing to enable. To turn on all tracing, use the following line instead:

/* for all modules (*), enable trace (+) for all levels (01234567) */
GT_set("*+01234567");

What About Software Trace?

Software Trace for BIOS 5 Based Versions of Codec Engine

A utility module you use to assist with software tracing in Codec Engine applications is the TraceUtil module. You can use this module for debugging and/or to collect real-time data.

Additionally, tools like SoC Analyzer can be developed to help display trace data. TraceUtil can be used to simplify the use of such tools.

TraceUtil lets you specify the amount of tracing you want and where you want it collected as follows:

  • At design time by setting configuration file attributes
  • At start time by setting environment variables
  • At run-time by writing command strings to a named UNIX pipe

TraceUtil manages the three kinds of tracing that Codec Engine modules can produce:

  • Tracing on the GPP side. Many Codec Engine and other GPP-side modules drop trace strings describing their state or warning and error messages.
  • Tracing on the DSP side. DSP-side modules may provide trace information that can be collected by TraceUtil on the GPP-side.
  • DSP/BIOS logging on the DSP side. DSP/BIOS provides the TRC and LOG modules to collect information about various DSP/BIOS system events such as task switching. You can use the TraceUtil module to enable such DSP/BIOS tracing remotely. Unlike the other kinds of trace, which are ASCII text, the DSP/BIOS log is a binary file.

As a supplement to TraceUtil, GPP-side code can also use printf(), or you can use the GNU Project Debugger (GDB) on GPP-side code.

Configuring TraceUtil at Design Time

To enable the TraceUtil module, your must add this line to your GPP application's configuration (.cfg) script. Any location in the script is fine.

var TraceUtil = xdc.useModule('ti.sdo.ce.utils.trace.TraceUtil');

The default TraceUtil settings cause the GPP application to:

  • Print all GPP-side errors and warnings to the standard output.
  • Collect DSP-side errors and warnings every 200 ms and print them to standard output.
  • Not enable or capture any DSP/BIOS logging.

Constants are provided to set trace attributes for NO_TRACING, DEFAULT_TRACING, SOCRATES_TRACING, and FULL_TRACING.

Instead of using the default, you can add the following line to your .cfg file to print information in the form the SoC Analyzer can use:

TraceUtil.attrs = TraceUtil.SOCRATES_TRACING;

The set of attributes configured with the SOCRATES_TRACING option enable SoC Analyzer tracing and DSP/BIOS logging. GPP-side trace information is stored in the /tmp/cearmlog.txt file, DSP-side trace information is placed in /tmp/cedsp0log.txt, and DSP/BIOS logging goes to /tmp/bioslog.dat. Polling is initially disabled.

With this option, the application begins running with tracing disabled. To turn tracing on, you or your program must write a command to turn tracing on to the trace command pipe. See the section Controlling Trace at Run-Time Through a Named Pipe for details.

Another option is to add the following line to your .cfg file to enable all types of tracing possible:

TraceUtil.attrs = TraceUtil.FULL_TRACING;

The output destinations are the same as for SOCRATES_TRACING, but FULL_TRACING enables all levels of trace for both the GPP and DSP.

You can further control the details of tracing behavior by setting individual TraceUtil.attrs fields in your .cfg file. For details, see the reference documentation for the ti.sdo.ce.utils.trace.TraceUtil module in the Configuration Reference, which is available at CE_INSTALL_DIR/xdoc/index.html

Supporting TraceUtil in Your Application's C Code

To collect the trace information that the DSP produces, you must add these lines of C code to your GPP application:

/* call TraceUtil_start() after CERuntime_init() */
TraceUtil_start(engineName);  /* engineName is a string */
 
...
 
TraceUtil_stop();   /* call at end of your app */

This code spawns a thread that collects all available DSP trace messages and dumps them to a file or standard output. (It also collects and stores DSP/BIOS LOG information if you want it to do so.)

Configuring the DSP Server for DSP/BIOS Logging

If you set TraceUtil on the GPP side to use DSP/BIOS logging, you must also have DSP/BIOS logging enabled in your DSP Server image. To do this, add the following line to your DSP Server's configuration script:

var LogServer = xdc.useModule('ti.sdo.ce.bioslog.LogServer');

If your DSP Server is incapable of DSP/BIOS logging, you will see GPP-side error/warning messages like the following:

LogClient_connect> Error: failed to locate server queue, Check if your DSP image has DSP/BIOS logging enabled
LogClient_fwriteLogs> Warning: not connected to the DSP/BIOS log server on the DSP, cannot collect any DSP/BIOS log data.

Configuring the DSP Server To Redirect Trace Output

When debugging a DSP Server or single-processor DSP application using Code Composer Studio (CCStudio), you can direct trace information to go directly to CCStudio's output window. To do this, modify the main() routine to make the following call before calling CERuntime_init():

GT_setprintf((GT_PrintFxn)printf);

This causes each trace call to map to the DSP standard I/O library's printf() function, which sends output to the CCStudio console window.

Note that the argument to the GT_setprintf() function can be any function that takes (char *format, …) arguments. So, for example, you could provide your own function that, for example, sends trace information to a serial port.

Configuring TraceUtil at Application Start Time

Before you run your TraceUtil-enabled application, you can set one or more of the following environment variables to override the TraceUtil attributes you specified in your .cfg script:

  • CE_TRACE. Mask for the GPP-side tracing. See the section Trace Mask Values for mask details. For example:

CE_TRACE="*=0567;OM-0"

  • CE_TRACEFILE. Specify the output file for GPP trace information. This can be a full path (for example, /tmp/local.txt) or a path relative to the executing application. If the file can't be opened (for example, if this points to a directory that doesn't exist), the trace goes to the standard output. For example:

CE_TRACE="trace/armtrace.txt";

  • CE_TRACEFILEFLAGS. Set file creation flags for all files to be opened. Use the standard fopen() flags—"a" means append; "w" means over-write. For example:

CE_TRACEFILEFLAGS="a"

  • TRACEUTIL_DSP0TRACEFILE. Specify the output file for DSP trace information. As with CE_TRACEFILE, this can be a full path or a path relative to the executing application. If the file cannot be opened, the trace goes to the standard output.
  • TRACEUTIL_DSP0BIOSFILE. Specify the output binary file for the DSP/BIOS log. This can be a full path or a path relative to the executing application. If the file cannot be opened, the log information is not collected.
  • TRACEUTIL_DSP0TRACEMASK. Mask for DSP-side tracing. See the section Trace Mask Values for mask details. For example:

TRACEUTIL_DSP0TRACEMASK="*+01;ti.bios=01234567"

  • TRACEUTIL_REFRESHPERIOD. Specify the number of milliseconds to sleep before the GPP-side collects the next set of DSP-side trace information. Your choice should vary depending on the amount of trace generated and the size of the trace logs. Failure to set this low enough may result in data loss.
  • TRACEUTIL_CMDPIPE. The name of a UNIX named pipe (for example, "fifo") to which the TraceUtil module should listen for runtime trace commands.
  • TRACEUTIL_VERBOSE. Set to 1 if you want TraceUtil to print the trace settings (masks and files) it is using and where it got them from. Set to 2 or higher to show more debugging information. In most cases, TRACEUTIL_VERBOSE=1 is recommended.

If you use the bash shell on Linux, it is especially convenient to set environment variables in the same line where you start your application, so they apply to that execution of the application only:

CE_TRACE="*+5" CE_TRACEFILE="mylog" TRACEUTIL_VERBOSE=1 ./app.out

Note that these environment variables are read only at application startup time. Changing them after the application is running has no effect.


Note: The CE_DSP0TRACE environment variable described in previous versions is ignored if you enable the TraceUtil module.


Controlling Trace at Run-Time Through a Named Pipe

If the TRACEUTIL_CMDPIPE environment variable is set to a valid name or if the TraceUtil.attrs.cmdPipeFile configuration option is set, the TraceUtil thread listens for any trace commands that appear in the pipe.

The SOCRATES_TRACING profile uses the command pipe feature. The pipe is /tmp/cecmdpipe by default, but the name can be overridden by setting the TRACEUTIL_CMDPIPE environment variable.

When you start a SoC Analyzer-enabled application, it initially provides no trace other than (potentially) warnings and errors.

Ways to override this initial behavior are:

  • Define the following environment variables before starting the application. See the section on Trace Mask Values for mask details.
CE_TRACE="*+5"
TRACEUTIL_DSP0TRACEMASK="*+5,ti.bios=3"
  • Issue the following command before running the application:
mkfifo /tmp/cecmdpipe; echo socrates=on > /tmp/cecmdpipe

The mkfifo command is necessary only for the first run; TraceUtil creates the pipe if it doesn't exist and doesn't delete it at the end.

When a SoC Analyzer-enabled application is running, you can turn tracing on by writing the following string to the /tmp/cecmdpipe file:

socrates=on

You can turn tracing off by writing the following string to the /tmp/cecmdpipe file:

socrates=off

The socrates=on and socrates=off pipe commands are aliases for a group of appropriate masks. These aliases are defined in the TraceUtil.xdc file.

The best way to write a string to the pipe is to use an open-write-close sequence (as opposed to keeping the pipe file open for writing throughout the session). The [create_pipe]->open_pipe->write_text->close_pipe sequence can be either done from the command line, from a script (as in the example above), or from a C program

The following list shows the supported trace pipe commands.

  • tracemask={GPP trace mask value} Sets the GPP-side trace mask. For example,

tracemask=*+01234567,OM-1

  • dsp0tracemask={DSP0 trace mask value} Sets the DSP0 trace mask. For example,

dsp0tracemask=*-1,ti.bios-012

  • refreshperiod={number of milliseconds} Sets the refresh period for DSP0 trace and log collection. If 0, there is no collection until a non-zero refreshperiod is specified. For example, refreshperiod=10
  • resetfiles (no arguments) Resets all open files for GPP trace, DSP0 trace, and DSP0 log (those that are currently in use) by truncating the files to 0 bytes.

Note that only one command per line should be written to the trace pipe. However, as was done for socrates=on, you can define—in the application’s configuration script—command pipe aliases to issue several pipe commands. For example:

var TraceUtil =    xdc.useModule('ti.sdo.ce.utils.trace.TraceUtil');
TraceUtil.attrs.cmdAliases = [
 {
 alias: "mycommands_1",
 cmds: [
           "resetfiles",
           "tracemask=*+5",
            "dsp0tracemask=*+5,ti.bios+3",
           "refreshperiod=200",
 ],
 },
 {
 alias: "mycommands_2",
 cmds: [
           "tracemask=*-5",
           "refreshperiod=0",
           "dsp0tracemask=*-5,ti.bios-3"
 ],
 },   /* and so on -- no limit on the number of aliases */
];

Trace Mask Values

Every VISA module can supply real-time trace output. This output can be enabled and disabled on a per-module basis at run-time. Each module can supply up to 8 levels of trace information. Several levels have universal meaning. For example, 0 corresponds to "entry" tracing (each module entry point displays its name and the arguments passed to it).

The NO_TRACING, DEFAULT_TRACING, SOCRATES_TRACING, and FULL_TRACING constants you can use in your application configuration provide easy ways to set commonly desired tracing levels. If you want custom trace levels for various modules, you can do that using the information in this section.

You can set trace masks (in a configuration file, environment variable, or command pipe) to a name/value pair or sequence of pairs. The name indicates the module whose tracing should be set, and the value indicates the trace levels enabled for that module.

For example, the following setting uses * (asterisk) as a wildcard to enable full Codec Engine tracing. This results in a lot of output, but is often useful in identifying what is going on internally.

setenv CE_TRACE "*=01234567"

You can also set modules to different trace levels in the same environment variable. To configure more than one module, you can separate masks with a semi-colon. Any module settings after the asterisk name/value pair override the wildcard setting.

For example, the following sets all modules to "1567", except "OM" (which you don't want to see), and "CV" (for which you want to see all information):

setenv CE_TRACE "*=1567;OM=;CV=01234567"

The following table lists the module names you can use in masks. It shows which modules apply to GPP trace (CE_TRACE) and which apply to DSP trace (TRACEUTIL_DSP0TRACEMASK):

Module Abbreviation Description Valid for GPP Valid for DSP
OC OSAL Communication. Abstracts messaging APIs across operating systems. Yes Yes
OP OSAL Process. Abstracts process APIs across operating systems and loads the server image to the DSP. Yes No
OM OSAL Memory. Abstracts memory APIs across operating systems. Yes Yes
OG OSAL Global. Abstracts generic APIs across operating systems. Yes Yes
OT OSAL Thread. Abstracts threading APIs across operating systems. Yes Yes
CE Codec Engine runtime APIs. Yes Yes
CS Server Runtime APIs. Yes No
CV Codec Engine VISA APIs. Yes Yes
CR Codec Engine - RMS. Codec Engine’s server daemon. No Yes
CN Codec Engine - Node. Instantiates codecs and communicates through custom skeletons. No Yes
LC Log Client. Used internally for acquiring BIOS LOG buffers from the DSP Yes No
LS Log Server. Used internally for acquiring BIOS LOG buffers on the DSP No Yes
XU XdmUtils. Used internally for managing XDM data structures Yes Yes
ti.sdo.ce.osal.AlgMem OSAL Algorithm. Used for creating, deleting, and controlling algorithms. Yes Yes
ti.sdo.ce.osal.power OSAL Power. Used in heterogeneous configurations to power on/off a server. Not supported in all releases. Yes Yes
ti.bios * Control the DSP/BIOS TRC module. No Yes
GT_prefix * Control what information is included in each trace line prefix. Yes Yes
GT_time * Control the format of timestamps in trace lines. Yes Yes

(*) The ti.bios, GT_prefix, and GT_time modules are special in that they are not affected by module wildcards in a trace mask.

You must name them directly to change their flags. For example:

setenv CE_TRACE "*=67;GT_prefix=12" setenv TRACEUTIL_DSP0TRACEMASK "*=567;ti.bios=012"

For the standard modules, the levels 0 through 7 report the following types of messages:

  • 7 = fatal errors
  • 6 = warnings
  • 5 = benchmarks
  • 4 through 1 = internal Codec Engine messages
  • 0 = function enter/exit reporting

For the special modules (ti.bios, GT_prefix, and GT_time) the levels have special meanings as follows:

Level ti.bios Module GT_prefix Module GT_time Module
none no DSP/BIOS logging no prefix microseconds in hex form (0xa0cf80fe)
0 TRC_LOGCLK short module name microseconds, in decimal form (4,021,348us)
1 TRC_LOGPRD long module name seconds (0.004s)
2 TRC_LOGSWI trace line class (level) delta in microseconds, excluding print time (+0,000,259us)
3 TRC_LOGTSK thread ID --
4 TRC_STSHWI stack address --
5 TRC_STSPRD with TRC_STSSWI and TRC_STSTSK time stamp --
6 TRC_USER0 -- --
7 TRC_USER1 -- --

For GT_prefix, the default levels used are 1, 3, and 5.

For GT_time, DSP time stamps are always in cycles, not in microseconds. Setting GT_time currently makes sense only on the GPP.

Software Trace for BIOS 6 Based Versions of Codec Engine

For BIOS 6 based versions of Codec Engine, GT trace has been replaced by xdc_runtime_Log and xdc_runtime_Diags APIs. Logging is enabled through configuration of the .cfg file and by calling xdc_runtime_Diags_setMask() at run-time. Note that TraceUtil is not supported.

Log Configuration

Logging can be configured in the application or server .cfg file, by including one of the utility Log setup files in ti/sdo/ce/examples/buildutils. Server .cfg files can include server_log.cfg, which sets up a circular buffer where Log_print() statements will be formatted. An application .cfg file can include common_log.cfg. These files can be included by adding a line to the .cfg file like:

// Set up logging
xdc.loadCapsule('ti/sdo/ce/examples/buildutils/common_log.cfg');

Alternatively, he common_log.cfg or server_log.cfg file can be copied and pasted into your .cfg file and modified there.

Unlike BIOS 6 modules, Codec Engine module logging cannot be enabled in the configuration file. Logging of Codec Engine modules can only be enabled at run-time, with the xdc.runtime function, Diags_setMask().


Trace Mask Values

Every Codec Engine module can produce real-time trace output that can be enabled and disabled at run-time on a per-module basis. Up to 9 levels of tracing are supported. Here is a description of the trace levels:

E – Function entry X – Funcion exit 1-4 – Informational trace 5 – Benchmarks 6 – Warnings 7 - Errors


Setting Trace Masks at Run-Time

To set the trace mask of a Codec Engine module you must call Diags_setMask() from run-time code, passing a string containing the module name and trace levels you want to enable. For example, the following code turns on warning and error trace for all Codec Engine modules:

#include <xdc/runtime/Diags.h>
 
Void main(Int argc, String argv[])
{
    /* Enable warnings and errors for all Codec Engine modules */
    Diags_setMask("ti.sdo.ce.%=67");
    ...
}

Note that the wildcard symbol is ‘%’. If we wanted to add function entry and exit trace to just the Engine module, we would add the following line:

    Diags_setMask("ti.sdo.ce.Engine+EX");

Note that ‘+’ is used to add trace levels to the current mask. You can use ‘-‘ to remove trace levels from a mask.


Easy Trace Enable for Linux Applications

Once your application has been configured for logging, real-time trace output can then be enabled at run-time either by enabling trace masks in “C” source code, or by setting environment variables (for Linux OS).

The simplest way to get trace for a host ARM application, is to set the environment variable, CE_DEBUG. Codec Engine initialization code internally calls Diags_setMask() for various modules and trace levels, depending on the value of CE_DEBUG. When CE_DEBUG is set to 1, warning and error trace will be enabled. Setting CE_DEBUG to 2 turns on most trace, leaving the largely unnecessary trace turned off. When CE_DEBUG is set to 3, all trace is enabled.

See http://processors.wiki.ti.com/index.php/CE_DEBUG for more details on CE_DEBUG usage.

Another environment variable that can be set to enable trace, is CE_TRACE. You can set it to the value of the string that you would have passed to Diags_setMask() in run-time code. For example,

    setenv CE_TRACE="ti.sdo.ce.%=67;ti.sdo.ce.Engine+EX"

Additional Documents and Resources

  • Codec Engine Server Integrator User's Guide (SPRUED5)
  • Codec Engine Algorithm Creator User's Guide (SPRUED6)
  • Codec Engine Application (API) Reference Guide CE_INSTALL_DIR/docs/html/index.html
  • Configuration Reference Guide. CE_INSTALL_DIR/xdoc/index.html
  • Example Build and Run Instructions. CE_INSTALL_DIR/examples/build_instructions.html
  • XDM API Reference.
  • XDAIS-DM (Digital Media) User Guide (SPRUEC8)
  • TMS320 DSP Algorithm Standard Rules and Guidelines (SPRU352)
  • TMS320 DSP Algorithm Standard API Reference (SPRU360)
  • TMS320 DSP Algorithm Standard Developer’s Guide (SPRU424)
  • TMS320 DSP Algorithm Standard Demonstration Application (SPRU361)
  • Codec Engine Overview