AWE Core OS 8.B.19 Documentation
|
This User Guide is designed to give an overview on the usage and functionality of the AWE Core OS™ product.
AWE Core OS is a hardware-independent, reconfigurable audio-processing engine that includes over 400 audio-processing modules. From mixers and filters to compressors and FFTs, these modules can be easily combined to create complex signal processing flows. The AWE Core OS engine operates by passing audio through a user-defined sequence of these modules, called a layout, as directed by configuration data at run-time.
While the AWE Core™ product is meant to be a portable library that targets bare-metal applications, AWE Core OS targets applications using a rich OS, such as Linux or Android. AWE Core OS extends the functionality of the AWE Core by taking advantage of features common to these Operating Systems, such as File I/O, TCP/IP Sockets, and multi-threading. Using native components of AWE Core OS, users can easily load layouts from AWBs, record input and output audio streams, process and log TCP/IP tuning command packets, and run complex, multi-sublayout signal processing flows across multiple cores.
Term | Definition |
---|---|
API | Application Programming Interface |
AWB | Audio Weaver configuration data binary file |
AWD | Audio Weaver configuration data design file |
AWE | Audio Weaver |
AWE OS Instance | The main AWE Core OS object |
AWS | Audio Weaver configuration data script file |
Clock Divider | Property of multi-rate layouts |
Layout | Audio Weaver signal processing flow |
Pump | Process audio using a loaded layout |
Tuning Interface | The interface by which AWE tuning commands/replies are transferred |
Sub-Layout | Independently processed section of a layout with a unique clock divider |
RT | Real-time |
This document will describe how to use various components of AWE Core OS in an audio processing application. For related documentation, see:
API Documentation: AWECoreOS API Doc.
Brief view of the API: See 'AWECoreOS_cheatsheet.pdf' in this package.
Details on underlying concepts and operation: Theory Of Operation
Help with ALSA Devices: Alsa Devices
Tuning protocol details: Tuning Protocol Doc.
Here are the basic steps to include AWE Core OS in an application.
Update application build project to link the following libraries:
pthread
(POSIX Threads), m
(Math), and rt
(Realtime Extensions) libraries.A number of variables must be defined before configuring/initializing the AWE Instance.
AWEOSInstance * g_AWEOSInstance;
AWEOSConfigParameters configParams;
static const void* moduleDescriptorTable[] = { LISTOFCLASSOBJECTS }; UINT32 moduleDescriptorTableSize = sizeof(moduleDescriptorTable) / sizeof(moduleDescriptorTable[0]);
AWE Core OS provides a function aweOS_getParamDefaults to fill a configuration parameter structure with default values. See AWEOSConfigParameters for details on the default values. It is recommended to always call aweOS_getParamDefaults in an application to get the default values, and then overwrite any of the defaults with user parameters as needed. The remainder of this section describes the individual configuration parameters in more detail.
Optionally overwrite any configuration parameters with values as appropriate for application
configParams.numThreads = 8;
configParams.sampleRate = 16000.0f;
#define AWE_BLOCK_SIZE 256 configParams.fundamentalBlockSize = AWE_BLOCK_SIZE;
configParams.fastHeapASize = FAST_HEAP_A_SIZE; configParams.fastHeapBSize = FAST_HEAP_B_SIZE; configParams.slowHeapSize = SLOW_HEAP_SIZE;
configParams.pFastHeapA = g_FastHeapA; configParams.pFastHeapB = g_FastHeapB; configParams.pSlowHeap = g_SlowHeap;
configParams.coreSpeed = 1.2e9f; configParams.profileSpeed = 10e6f;
configParams.pName = “MyTarget”;
// This example represents a date of April 6, 2020 configParams.userVersion = (UINT32) 04062020;If not planning on using aweOS_tuningSocketOpen, allocate and assign packet buffer(s) to be used by user created tuning interface.
#define PACKET_BUFFER_SIZE 264 ... configParams.packetBufferSize = PACKET_BUFFER_SIZE;
UINT32 packetBuffer[PACKET_BUFFER_SIZE]; ... configParams.pPacketBuffer = packetBuffer;
UINT32 packetReplyBuffer[PACKET_BUFFER_SIZE]; ... configParams.pReplyBuffer = packetReplyBuffer;
Once the instance parameters are configured correctly, initialize the AWE OS Instance by calling aweOS_init. If aweOS_init is called before the configuration parameter structure is fully configured, the initialization will fail. The module table and table size must also be provided to the initialization function. Note that a pointer to the AWE OS Instance pointer (AKA Double Pointer) must be the first argument.
int ret = aweOS_init(&g_AWEOSInstance, &configParams, moduleDescriptorTable, moduleDescriptorTableSize);
Once the AWEOSInstance is initialized, the next step is to enable a tuning interface. One of the most important components of an AWE Core OS application, a tuning interface provides a communication channel between the AWE OS Instance and an application like AWE Server running on the host PC. This communication channel is essential as it provides key audio tuning and debugging capabilities. For example, tuning commands can instantiate on the target a layout generated in AWE Designer, or can set and query the values of module parameters in real-time.
The AWE Core OS API includes a built-in TCP/IP Socket based tuning interface that can be started with aweOS_tuningSocketOpen. This function can be used for any target with an ethernet or WiFi connection, and lets the user specify a port number that can be used to connect to an AWE Server instance on the host PC. The tuning interface can be closed by the application using aweOS_tuningSocketClose, and all of the packets sent and received can be logged using aweOS_tuningLoggingEnable. If using this built-in tuning interface, then no more action needs to be taken by the application.
Most AWE Core OS applications will use the built-in aweOS_tuningSocketOpen function to open a TCP/IP socket that can be used to communicate with AWE Server.
INT32 interfaceRet = aweOS_tuningSocketOpen(&g_AWEOSInstance, PORTNO, 1); if (interfaceRet == E_SUCCESS) { printf("Opened TCP tuning interface on port %d: Waiting for AWE Server Connection from PC... \n", PORTNO); }
See aweOS_tuningSocketClose and aweOS_tuningLoggingEnable for additional control functions for the built-in tuning interface.
If TCP/IP communication is not available on the target hardware, then the application will have to implement a custom tuning interface. Different platforms will support different transport protocols for the tuning interface. AWE Server supports USB, TCP/IP (sockets), RS232, and SPI communications.
Any implementation of a tuning interface will follow these general steps
packetBuffer
and, optionally, packetReplyBuffer
. Assign these to the configuration parameters structure before calling aweOS_init.packetBuffer
and then call aweOS_tuningPacketProcess.packetBuffer
, and then generate a reply in the registered replyBuffer
buffer. If the same buffer is used for both receive and reply, the reply message will overwrite the original command directly in packetBuffer
.readPacket(packetBuffer, 264) aweOS_tuningPacketProcess(&g_AWEOSInstance) writePacket(packetReplyBuffer, 264)The TX/RX of the packet buffers will differ between each tuning protocol. The rest of this document will explain the message structure and protocol of Audio Weaver tuning commands. It will also explain the details needed to implement each supported transport method's protocol.
For a complete list of Audio Weaver tuning commands, see the file Tuning Protocol Doc.
The next step is to integrate real-time audio. AWE Core OS aside, real-time audio can be a tricky topic. Before attempting to integrate real-time audio into an AWE Core OS system, please ensure that the integrator has a basic understanding of digital audio and real-time audio processing. Here are some helpful links.
http://www.rossbencina.com/code
Giulio Moro - Assessing the stability of audio code for real time low latency embedded processing
The data type of the input and output audio data is determined by the audio hardware. Typically, digital audio is represented as fixed point data in 16, 24, or 32 bit depth. The audio sample formats _SampleType supported by AWE Core OS's aweOS_audioImportSamples and aweOS_audioExportSamples functions are:
Internally, all inputs and outputs to Audio Weaver layouts are of type Sample32bit, also referred to as fract32 within a layout. This is done to guarantee portability of any signal processing layout to any target system, regardless of the hardware's native audio data type. The aweOS_audioImportSamples and aweOS_audioExportSamples functions will convert to and from the Sample32bit data as needed based on the _SampleType argument. If the target's native audio sample formatting is not one of those listed above, then the program will have to convert the data to one of the supported types before using the import and export functions.
Since some common target systems natively support floating point audio, helper functions are provided to convert between float<-->fract32 in AWECoreUtils.h, which is provided in the AWE Core OS package. See the sample-by-sample conversion functions float_to_fract32 and fract32_to_float. See the API in doc AWECoreUtils.h for more info.
The AWE Core OS library allows audio to be processed at multiple block rates. For example, consider a system with two processing paths: one that processes time-domain data in blocks of 32 samples and another that processes frequency-domain data in blocks of 64 samples but at 1/2 the block rate of the time-domain path. Each of these processing paths is called a sublayout, and each has a clock divider property equal to the inverse of the relative block rates. In this example, the sublayout with 32 samples has a clock divider of 1, and the sublayout with 64 samples has a clock divider of 2. Such a system is shown in the figure below. It uses BufferUp and BufferDown modules to connect the different block size domains. These modules effectively partition the layout into 2 sublayouts operating at different block rates. When this layout is pumped, the sublayout of 64 samples will only be processed half as often as the sublayout of 32 samples.
Real-time constraints dictate that if the 64 sample sub-layout is executed in the same context as the 32 sample sub-layout, both will need to complete processing before the next block of 32 samples arrives or real-time will be broken. This imposes an unnecessarily strict constraint on the 64 sample sub-layout – its processing need only be completed before the next block of 64 samples is accumulated. Thus, its processing time can, in principle, be spread over 2 of the 32 sample block times without breaking real-time requirements. The internal scheduling of layout pumping in AWE Core OS handles this spreading of sublayout execution times to allow for effective multi-rate processing.
See the CPU Affinity for Multi-Rate Designs section for more info about threading and CPU affinity.
When profiling multi-rate layouts, AWE Designer's Manual Profiling tool may provide more accurate results than the real-time profiling tools. The Manual Profiling Tool uses a simulated audio callback to manually pump samples through each sublayout in a multi-rate design. The tool can be found in AWE Designer's top bar, "Tools->Manual Profile Layout".
At this point, the application should be able to load a layout from AWE Designer and run it on the target via the Tuning Interface. Once a layout in Designer has been tuned and finalized, the application can be updated to load the layout from an AWB file saved to the filesystem, or from a C array compiled directly into the application.
From the viewpoint of AWE Core OS, the signal processing layout is described by a data array of binary Audio Weaver commands. This command array is generated by AWE Designer using the Tools/Generate Target Files menu item and enabling the [BaseName]_InitAWB checkbox. The layout is loaded into an AWE Core OS instance by making a call to aweOS_loadAWBFromArray with the command array, its size in 32-bit words, and the AWEOSInstance as arguments. If an error occurs during loading, then the offset of the offending AWB command is returned to the pErrorOffset argument.
INT32 err = aweOS_loadAWBFromArray (pAWE, pCommands,arraySize, &nErrOffset ); if (err) { // report the error printf(“error code %d due to command at position %u\n”, err, nErrOffset); // handle the error ... }
aweOS_loadAWBFromArray will load and process the entire array of binary commands included in the pCommands array. If an array of commands needs to be loaded on a remote instance, it can be loaded command by command with the awe_getNextAWBCmd() helper function in AWECoreUtils.h. Each command is parsed so that they can be routed to the remote instance.
To generate an AWB file from a layout in Designer, use the Tools/Generate Target Files menu and enable the [BaseName].awb checkbox. This .awb file can be saved to the filesystem on the target and aweOS_loadAWBFile can be used to load and process the commands therein. Loading an AWB from a file rather than a C array has the advantage that an AWB file can be swapped out with an updated design without having to recompile the application.
The AWE Core OS library handles the scheduling and prioritization of all the internal processing threads. The user application must only set the schedule and priority of the audio loop function, most often implemented as part of the audio driver's callback function. Since each OS has different scheduling and prioritization models, the code to set the audio loop to a high priority level will be application specific. For a Linux system where the application can be executed with root permissions, the following code in the audio loop context sets the priority to a real-time level and the scheduling policy to SCHED_FIFO.
int max_priority = sched_get_priority_max(SCHED_FIFO); struct sched_param audio_sched_param = {0}; pthread_t currentHandle = pthread_self(); int target_priority = max_priority - 2; audio_sched_param.sched_priority = target_priority; pthread_setschedparam(currentHandle, SCHED_FIFO, &audio_sched_param);
A control interface lets the system interact with modules in the running layout directly from the firmware. To access the layout from the API, use Designer to generate a control-interface header file for the layout. Then, use the following API calls define the functionality of the control interface:
Get/set a value of a module: aweOS_ctrlGetValue() and aweOS_ctrlSetValue()
INT32 aweOS_ctrlGetValue(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length); INT32 aweOS_ctrlSetValue(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length);
Get/Set the status of a module (bypassed, active, inactive, muted): aweOS_ctrlSetStatus() and aweOS_ctrlGetStatus()
INT32 aweOS_ctrlSetStatus(const AWEOSInstance *pAWEOS, UINT32 handle, UINT32 *status); INT32 aweOS_ctrlGetStatus(const AWEOSInstance *pAWEOS, UINT32 handle, UINT32 *status);
Check if a module exists, and if so return its ClassID: aweOS_ctrlGetModuleClass()
INT32 aweOS_ctrlGetModuleClass(const AWEOSInstance *pAWEOS, UINT32 handle, UINT32 *pClassID);
The following functions provide finer grained control over how module variables get set and are for advanced users: aweOS_ctrlSetValueMask() and aweOS_ctrlGetValueMask()
INT32 aweOS_ctrlSetValueMask(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length, UINT32 mask); INT32 aweOS_ctrlGetValueMask(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length, UINT32 mask);
To access a module and control it via the Control Interface,
handle, length
arguments will all be defined in the [BaseName]_ControlInterface.h file that was generated. See the API doc for details about the control functions arguments/return values.Generally, interacting with modules should follow this flow:
// Does the current AWE model have a Scaler module with this control object ID? if (aweOS_ctrlGetModuleClass(g_AWEOSInstance, AWE_Scaler1_gain_HANDLE, &classId) == E_SUCCESS) { // Check that module assigned this object ID is of module class Scaler if (classID == AWE_Scaler1_classID) { // Query Scaler1 module's current gain value (value is a scaler) aweOS_ctrlGetValue(g_AWEOSInstance, AWE_Scaler1_gain_HANDLE, 0.5, 0, AWE_Scaler1_gain_SIZE); } }
See the LayoutControl.c app for a full example of controlling a module directly from the application.
AWE Core OS supports multiple levels of memory optimization including custom heaps and reduced module sets. The audio processing threads created by AWE Core OS can also be optimized by fixing their cpu affinity at runtime.
AWE Core OS creates default heaps of size 5000000 words when using the aweOS_getParamDefaults API. If the configuration parameters heap members (heap pointers and heap sizes) are left untouched before aweOS_init, then these default heaps are allocated and assigned to the AWEOSInstance. In most development situations, these default heaps will be sufficient and the user can likely leave them untouched.
Although the default heaps are OK for development, the user may want to reduce the size of the heaps to save memory usage at production time. In some situations, they may also want complete control over the heap's memory location itself, rather than letting AWE Core OS allocate the heap internally.
Overwrite any of the AWEOSConfigParameter's heap size members (fastHeapASize, fastHeapBSize, slowHeapSize) before calling aweOS_init to use a custom sized heap. When a heap size parameter is overwritten, AWE Core OS will internally allocate and assign heaps of the specified sizes. Note that the heap memory location itself is still internal to the AWE Core OS library.
If a user wants complete control over a heap (size AND memory location), AWEOSConfigParameter's heap pointer members (pSlowHeap, pFastHeapA, pFastHeapB) can be overwritten with pointers to user allocated memory.
If the AWEOSConfigParameter's heap pointers are being overwritten, then the heap size members must also be overwritten to the size of the custom heap. Passing in a custom heap and no size will result in an error when initializing.
UINT32 customSlowHeap[MY_CUSTOM_SIZE]; configParams.pSlowHeap = customSlowHeap; configParams.slowHeapSize = MY_CUSTOM_SIZE;
AWE Designer can generate the absolute smallest heap sizes needed for a specific layout via Tools->Generate Target Files->[BaseName]_HeapSize.h.
This can greatly reduce the memory footprint and aid in optimizing a system. Use these generated heap sizes to overwrite the heap size configuration parameters of AWE Core OS.
The ModuleList.h file that is delivered with AWE Core OS contains a complete set of Audio Weaver modules available in the library. This is great for designing layouts during development, but when the design is complete, the user may want to remove the unneeded modules to reduce the size of the application.
To trim the module list, simply remove the unused modules from the LISTOFCLASSOBJECTS in ModuleList.h. The module list is passed in as an argument to the aweOS_init function at build time, and only the module's in the list will be included in the application.
See the Declaring the Required Variables section for more implementation details regarding the module list.
AWE Designer can generate a new ModuleList.h file that contains only the module's used in a given layout. From Designer do Tools->Generate Target Files->[BaseName]_ModuleList.h.
Replace the default ModuleList.h with this trimmed version, and rebuild the application.
3rd party or user created modules can also be added to the application by adding the modules to ModuleList.h and linking the module libraries to the executable.
AWE Core OS internally creates/manages a unique processing thread for each sublayout in a design, as well as a mandatory "workThread" and an optional "socketThread" per AWE Core OS instance. For example, the design shown in the Multi-Rate section, consisting of a single bufferup/down module (1 clockdivider + base layout), would create two pumpthreads when not running in Low Latency (LL) mode. If running in LL mode, a single pumpthread (awe_pumpthread0) is created only for the second sublayout, because the baselayout is pumping within the user audio thread. See the Low Latency Pumping section for more information.
It is important to note that these threads are independent of the user's audio callback thread (where aweOS_audioImportSamples / aweOS_audioExportSamples and aweOS_audioPumpAll are being invoked). The user's audio callback fills the aweOS import buffers with new samples, calls aweOS_audioPumpAll, and then exports the latest available data from the AWE OS output buffers. The aweOS_audioPumpAll function then coordinates the created pumpthreads to execute as needed. Normally, the aweOS_audioPumpAll function does not block until the individual pump threads are completed, and returns immediately once the pump threads have been signaled. See the Low Latency Pumping section for more information.
In design described above, the following threads would be seen in top/htop in a Linux system while NOT running in Low Latency Mode.
./<AWE Core OS Application Parent Process> <myAudioCallbackName> awe_pumpthread0 awe_pumpthread1 (if LL mode, then this would not exist) awe_workThread (not an audio thread, but also high priority and time critical) awe_socketThread (not an audio thread, created upon @ref aweOS_tuningSocketOpen)
Note: if htop is not showing the names listed above, go to Setup (F2) -> Display Options and enable "Show custom thread names" as seen below. Enabling the Tree view display option is also helpful.
AWE Core OS provides an API aweOS_getThreadPIDs() to retrieve internally created thread identifiers. The PIDs of all internal AWE Core OS pump threads, work thread, and tuning socket threads will be provided. See the API documentation of aweOS_getThreadPIDs() for more details, as well as the RTAudio-alsa.c example, which demonstrates setting CPU affinity within an AWE Core OS integration.
Each operating system will have different methods to update the priority the process is given by the scheduler. The following sections describe Linux specific operations but the general theory can be applied to any OS.
Because the Linux kernel decides the scheduling or processes, the audio threads and the workThread need to be run at the highest possible priority so as not to be preempted by less critical processes. AWE Core OS internally sets the pumpthread's priority level based on the audiocallback's priority level (cascading downward). For example, if the audiocallback thread shown above was at priority level 99, then the pumpthread 0 would be set to 98, pumpthread1 to 97, and the workThread to 96.
This requires that the user places their audiocallback thread at a high enough priority to leave room for the number of supported threads (AWEOSConfigParameters.numThreads), as well as the workThread. To ensure that the priority is high enough and that the audio threads aren't unnecessarily interrupted, it is recommended to simply set the audiocallback's priority to the maximum that the system allows. The audiocallback should always be the highest priority, and the workthread will always be just under the lowest pumpthread's priority level.
The Linux kernel also has control over which CPU a thread executes on, and may choose to migrate the threads to different CPUs at any point. This can be problematic for a number of reasons, but is easily avoided by fixing CPU affinity for the entire process, or for individual threads. To fix all threads in an AWE Core OS application to a single CPU, simply run the executable with taskset and the desired physical CPU.
taskset -c 0 ./RTAudio-alsa
The command above would run the RTAudio-alsa example app and all of its threads exclusively on CPU 1. This may be safer than letting the kernel migrate threads freely, but it also does not fully utilize the processing capabilities of multi-core hardware.
Generally, audio data is dropped when a physical CPU's load approaches/exceeds 100%. Forcing all of the AWE Core OS threads to share a single CPU will cause audio dropouts on that CPU much sooner than if the pumpthreads were distributed to their own CPU's. When everything is forced to one CPU, the other physical CPUs are sitting idle and unutilized. This is highly inefficient, especially in a Multi-Rate design where multiple pumpthreads are running.
AWE Core OS Systems can be highly optimized by assigning strict CPU affinity to its internal threads. CPU affinity can be set from within an AWE Core OS integration (see aweOS_getThreadPIDs and usage in RTAudio-alsa.c ). Affinity can also be set after an application is running as described below...
The following shell script will search for a parent process containing the string "RTAudio", and all child processes containing the string "pumpthread". It sets the pumpthreads to their own dedicated CPUs.
This script assumes that 4 physical cores are available, and therefore assigns a max of 4 pumpthreads. It also assumes that the application name contains the string "RTAudio" (For example, the RTAudio-alsa sample app)
#!/bin/bash parentPIDgrep=$(pgrep --list-name RTAudio) parentPIDarray=( $parentPIDgrep ) parentPID=${parentPIDarray[0]} parentProcessName=${parentPIDarray[0]} echo "Found parent process $parentProcessName at pid $parentPID" processList="$(ps H -o 'tid comm' $parentPID)" oldIFS="$IFS" IFS=' ' IFS=${IFS:0:1} processes=( $processList ) IFS="$oldIFS" declare -i PUMPTHREAD_COUNTER PUMPTHREAD_COUNTER=0 for process in "${!processes[@]}" do if [[ ${processes[process]} =~ "pumpthread" ]]; then echo "found pumpthread ${processes[process]}" PUMPTHREAD_PID_ARRAY[$PUMPTHREAD_COUNTER]=${processes[process]} PUMPTHREAD_COUNTER=PUMPTHREAD_COUNTER+1 fi done for pumpthread in "${!PUMPTHREAD_PID_ARRAY[@]}" do fullElement=${PUMPTHREAD_PID_ARRAY[pumpthread]} splitPID=( $fullElement ) PID=${splitPID[0]} threadIdx=$pumpthread if [[ $threadIdx == "0" ]]; then tasksetout=$(taskset -p 0x11 $PID) elif [[ $threadIdx == "1" ]]; then tasksetout=$(taskset -p 0x12 $PID) elif [[ $threadIdx == "2" ]]; then tasksetout=$(taskset -p 0x14 $PID) elif [[ $threadIdx == "3" ]]; then tasksetout=$(taskset -p 0x18 $PID) fi declare -i COREINT COREINT=threadIdx COREINT=COREINT+1 echo "set $PID cpu affinity to core $COREINT" done
Provided they have the required permissions, the user has total control of thread CPU affinity with taskset, and this script can be customized to suit a specific system.
AWE Core OS will use the CPU affinity of each processing thread to improve the accuracy of the reported profiling for multi-threaded layouts. If the CPU affinity for all pump threads is locked to just one CPU, then AWE Core OS will be able to correct profiling calculations for each pump thread to avoid double counting cycles in the case of preemption of threads running on the same core. For accurate profiling with AWE Core OS, it is always recommended to set the CPU affinity to a single core for all threads using any of the methods described above.
Before setting up a multi instance tuning interface, it's recommended to first use and understand a single instance tuning interface. The HelloWorld.c example app is a good place to start.
AWE Core OS systems can contain multiple AWEOS Instances. Though each instance is unique and independent, AWE Core OS supports a single TCP tuning interface socket for up to 31 instances. This feature is demonstrated in the Multi-Instance example app as "single socket" mode. When AWE Server is connected to a multi-instance single socket, a dropdown will appear in the top left corner (the same is true in Designer). Use these dropdown menus to toggle between sending commands to individual AWEOS Instances.
AWE Core OS also supports opening a tuning socket for each instance on unique port numbers (demonstrated in the Multi-Instance example app as "twosocket" mode). While operating in this mode, only one instance can be controlled at a time from the PC. To control both instances, AWE Server needs to change its connection back and forth, or multiple PCs must be connected to the device.
For more implementation details of multiple sockets, please see the Multi-Instance.c example app.
For accurate profiling, link all the AWE Core OS instances into the framework with the API aweOS_setInstancesInfo. This will enable accurate profiling across all instances and pump threads provided that CPU affinities are set for all instances and threads. See the CPU Affinity - Profiling section for more details. For more details on how to use this API, see the Multi-Instance.c example app.
If a custom tuning interface is desired (RS232, USB, etc.), then the application creator is responsible for the routing/processing of AWE Core OS tuning commands between multiple AWEOSInstances. This is described in theTuning Protocol Doc section.
This section discusses only the latency caused by the AWE Core OS framework. The signal processing flow, the firmware, and the hardware can all introduce additional latency to the overall system. In this discussion, a "block" of latency refers to the layout block size and the systems sample rate, unless otherwise specified. For example, with a layout block size of 256 and a sample rate of 48 kHz, the latency through the AWE Core system will be in multiples of blocks of (256 / 48000) = 5.33 ms. AWE Core OS can introduce up to 2 block’s of latency, but can also be configured to achieve 0 blocks of latency under certain conditions.
To achieve 0 blocks of AWEOS-induced latency, the following conditions must be met:
Order of audio API calls: The order of AWE Core OS audio processing API calls in the applications audio callback should always be import -> pump -> export, as in the following:
aweOS_audioImportSamples —> aweOS_audioPumpAll —> aweOS_audioExportSamples
If neither of the above are true, then the total latency will be 2 layout block sizes.
AWE Core OS has an internal double buffering scheme on the input and output pins to handle situations where a layout’s block size is a multiple of the AWEOSInstance’s fundamental block size. For example, a system with a 64 BS layout running on a 32 fundamental BS AWEOSInstance must complete two, 32-sample audio callbacks before actually pumping audio through the layout (to have accumulated the 64 samples required by the layout). Double buffering is used in this situation in order to store the next frames of data in one buffer, while the processing occurs on the data in the other buffer. Two blocks of latency are introduced by this double buffering of the input and output pins.
However, in a situation where a layout BS is the same as a fundamental BS, the double buffering scheme is not required and two blocks of latency can be avoided. AWE Core OS has an internal mechanism to check if the layout and fundamental block sizes are equivalent at layout runtime, and will automatically bypass the double buffering to eliminate the introduced latency. Less memory is also consumed under this condition as only a single buffer can be used at the input and output pins.
Fundamentally, 0 blocks of latency can only be achieved through AWE Core OS if all of the audio processing occurs in a single callback. Based on this, the application's audio processing code must implement the following order of API calls to achieve the lowest possible latency:
aweOS_audioImportSamples aweOS_audioPumpAll aweOS_audioExportSamples
Different orders of API calls will still operate correctly from a processing standpoint, but may not achieve the lowest possible latency.
When the equivalent block size condition is met, AWE Core OS will handle pumping of the layout slightly differently than in the general, non-matching block size case. In order to guarantee minimal latency, AWE Core OS will pump the first sublayout directly in the aweOS_audioPumpAll function, rather than signaling a background thread to pump the layout in a separate context. This 'in-line' pumping of the first layout causes the aweOS_audioPumpAll function to block until the first sublayout is completed, which guarantees that the data returned by the subsequent call to aweOS_audioExportSamples contains the processed data, input by the previous aweOS_audioImportSamples call. Other non Low Latency paths in the layout (clockdivided sublayouts) will still be executed in their respective lower priority threads.
Although most of the AWE Core OS API functions take the AWEOSInstance argument as a single pointer (pass by reference), a few of the functions require a double pointer (pass by reference to reference). The functions that require double pointer arguments are aweOS_init and aweOS_tuningSocketOpen. For example aweOS_audioIsStarted would be called like...
AWEOSInstance *g_AWEOSInstance; { UINT32 isStarted = aweOS_audioIsStarted(g_AWEOSInstance); }
while aweOS_init would be called like...
AWEOSInstance *g_AWEOSInstance; { aweOS_init(&g_AWEOSInstance, &configParams, moduleDescriptorTable, moduleDescriptorTableSize); }
Notice that the instance itself is declared as a pointer, so for the functions that require a double pointer, we pass a reference to that pointer.
If the AWEOSConfigParameter's heap pointers are being overwritten, then the heap size members must also be overwritten to the size of the custom heap. If the user wants to use specific memory regions for heaps, then the application must allocate the memory for the heaps, set the heap pointers to this memory, and set the heapSize members of the AWEOSConfigParameter's to the appropriate sizes. Passing in a custom heap and a size of zero will result in an error when initializing, but passing mismatched heap pointers and sizes will likely lead to memory corruption and system crash.
For information about how to use the RTAudio-alsa sample app with a specific audio device, please see
In order to assist in debugging audio paths and layout input/outputs, there is an option to enable recording of the audio that is processed by the loaded layout using the function aweOS_audioRecordingEnable. This will produce .wav files for both the input and output audio of the layout and can be useful for detecting issues with audio drivers and debugging unexpected results. Also refer to aweOS_audioRecordingDisable and aweOS_audioRecordingRegisterNotificationCallback for more details.