Skip to content

RTAudio-alsa.c

Examples

/****************************************************************************
 *
 *      RTAudio-alsa
 *      --------------
 *
 ****************************************************************************
 *
 *  Description:    Pump realtime audio through a layout using the linux alsa API
 *
 *  Copyright: (c) 2022 DSP Concepts, Inc. All rights reserved.
 *                                  3235 Kifer Road
 *                                  Santa Clara, CA 95054-1527
 *
 * This example is demonstrates integration of a realtime audio framework, specifically the alsa API. 
 * It brings in samples of audio from an alsa callback, and pumps them through a layout with the aweOS_audioPumpAll function. 
 * This example is meant to show the pumpAll function, and the surrounding import/exportSamples API's
 * 
 * NOTE: AudioStream.c and all alsa related code written by DSPC Concepts is simply for reference, and is not considered robust or full featured...
 * The alsa code is meant to be a bare minimum, and the focus of this example should be on this file, and the AWE Core OS API functions. 
 * See the alsa api doc for more details about realtime audio with alsa
 * 
 * NOTE: This example must be run with superuser priorities, to ensure that we can we set scheduling parameters of the internal threads. 
 * Please give privileges or run with sudo. aweOS_audioPumpAll will return E_SCHEDULER_PERMISSION_DENIED as a warning.
 * 
 * By default, this example opens the alsa input and output devices "hw:0,0", but any device id can be passed in with cmd line arguments. The device id string must be in "" 
 *
 * NOTE: This device id string is an alsa specific parameter (https://www.alsa-project.org/wiki/Documentation), where "hw:" means the device type, 0 is Card #0, and 0 is Device #0. 
 * To find the device id string of an output device, enter the command "aplay -l" into the console. This will display alsa's list of all available output Cards/Devices
 * Then, enter aplay -L to see the available types. A usb audio device may support both types "hw" and "plughw". For USB devices, use "plughw"
 * For example, a USB device on Card 1, Device 0 would take the cmd line arg -outputdevice:"plughw:1,0"
 * The process is the same for finding the input device id, but use the commands "arecord -l" and "arecord -L" instead of "aplay -l" and "aplay -L". 
 * For example, a USB device on Card 1, Device 0 would take the cmd line arg -inputdevice:"plughw:1,0"
 *
 * Block size, sample rate, and channel counts of the HW device can also be set with cmd line arguments.
 * Audio logging can also be enabled with cmd line options, but is disabled by default.
 *
 * The example opens a tuning interface on port 15002, unless specified otherwise with the -portno: cmd line option.
 * An awb can be loaded at run time with the -load: option. If no awb file path is given, then the app waits for one over the tuning interface.
 *    
***************************************************************************/
#define _GNU_SOURCE
#include <pthread.h>
#include <sys/prctl.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/sysinfo.h>

#include <sys/syscall.h>
#ifndef SYS_gettid
#warning "SYS_gettid unavailable on this system"
#else
#define gettid() ((pid_t)syscall(SYS_gettid))
#endif

#include "AWECoreOS.h"
#include "AudioStream.h"
#include "ModuleList.h"


AudioStream *as;
AWEOSInstance *g_AWEOSInstance; 
static AWEOSConfigParameters configParams; 

UINT32 hwInputChannels = 1;
UINT32 hwOutputChannels = 2;  
char* inputDeviceName;
char* outputDeviceName;
UINT32 portNo = 15002;
UINT32 fundamentalBlockSize = 32;
double sampleRate = 48000.0;
UINT32 enableAudioLogging = 0;
const char* awbPath;
UINT32 loadAWB = 0;
float coreSpeed = 1e9f;
float profileSpeed = 10e6f;
UINT32 setCPUs;
AWEOSThreadPIDs_t threadPIDs;

UINT32 firstCallback = 1;

void destroyApp()
{
    INT32 ret; 
    // destroy function does most of the teardown of the system for us
    ret = aweOS_destroy(&g_AWEOSInstance);
    if (E_SUCCESS != ret) 
    {   
        printf("aweOS_destroy: failed with the error = %d %s\n", ret, aweOS_errorToString(ret));
    }
}

void sig_handler(int signo)
{   
    if (signo == SIGINT || signo == SIGPIPE || signo == SIGKILL)
    {
        // Cought termination signal
        destroyApp();
    }
    printf("Exiting RTAudio-alsa\n");
    exit(0);    
}

static const void* moduleDescriptorTable[] =
{
    LISTOFCLASSOBJECTS
};
UINT32 moduleDescriptorTableSize = sizeof(moduleDescriptorTable) / sizeof(moduleDescriptorTable[0]);

INT32 audioStartCallback(AWEOSInstance *pAWEOS)
{
    INT32 i, availableProcessors, cpuToSet, ret;
    cpu_set_t pumpthread_set;  

    printf("Audio Start Callback: Starting audio stream \n");

    if (setCPUs)
    {
        // Get the internally running thread PIDs and print them
        ret = aweOS_getThreadPIDs(g_AWEOSInstance, &threadPIDs);
        if (ret != 0)
        {
            printf("aweOS_getThreadPIDs failed with error %d. Not setting CPU affinity... \n ", ret);
        }
        else
        {
            availableProcessors =  get_nprocs();
            for (i = 0; i < threadPIDs.numPumpThreads; i++)
            {
                // Audio streaming thread always set to first cpu
                cpuToSet = (i + 1) % availableProcessors;
                CPU_ZERO(&pumpthread_set);
                CPU_SET(cpuToSet, &pumpthread_set);    

                ret = sched_setaffinity(threadPIDs.pumpThreadPIDs[i], sizeof(cpu_set_t), &pumpthread_set);  
                if (ret == 0)
                {
                    printf("Set pumpThread %d (PID = %u) to CPU %d \n", i, threadPIDs.pumpThreadPIDs[i], cpuToSet); 
                }
                else 
                {
                    printf("Could not set CPU affinity of pumpThread %d to CPU %d , errno: %d \n", i, cpuToSet, errno);
                }       
            }
        }
    }

    // Start audio stream
    AudioStream_start(as); 


    return 0;   
}
//the user defined callback for audio stop that will be passed into the param structure. Stop PA here
INT32 audioStopCallback(AWEOSInstance *pAWEOS)
{
    printf("Audio Stop Callback: Stopping audio stream \n");
    AudioStream_stop(as);
    firstCallback = 1;
    return 0;
}

void audioRecordNotifyCallback(AWEOSAudioRecordNotification_t * recordingNotification)
{
    if (recordingNotification->notificationStatus == INPUT_OVERRUN)
    {
        printf("Got xrun on audio recording input stream: total xruns = %u, time of xrun = %f\n", recordingNotification->xruns, recordingNotification->xrunTime);
    }
    else if (recordingNotification->notificationStatus == OUTPUT_OVERRUN)
    {
        printf("Got xrun on audio recording output stream: total xruns = %u, time of xrun = %f\n", recordingNotification->xruns, recordingNotification->xrunTime);
    }
    else
    {
        printf("Got audio recording error notification: %d\n", recordingNotification->error);
    }
}

void loadAWBFile(AWEOSInstance *pAWEOS, const char* filePath)
{
    //Load the AWB from the file
    UINT32 position = 0;
    INT32 ret = aweOS_loadAWBFile(pAWEOS,filePath, &position);
    if (0 == ret) 
    {
        printf("The layout %s loaded succesfully\n", filePath);
    }
    else 
    {
        printf("The layout %s downloaded unsuccessfully at position %u with error = %s \n", filePath, position, strerror(ret));
        exit(1);
    }   
}

//AudioStream audio callback function. This is where the importing/exporting/pumping will happen
AudioStream_CallbackResult audio_callback(const void *inputBuffer, void *outputBuffer,
    unsigned long framesPerBuffer,
    AudioStream_CallbackFlag statusFlags,
    void *userData)
{   
    /*suppress warnings for unused variables timeinfo, userData */
    (void)userData; 
    UINT32 i, j;
    INT32 pumpRet; //return for pump
    if (statusFlags != 0)
    {
        // Indicate an xrun. These may not appear until after the audio stream is stopped. Enable logging for more details
        printf("xrun ");
        return AudioStream_CallbackResult_Continue; //Even though there was an xrun, tell the stream to continue. This will cause audio dropouts under high CPU load when xruns are occuring.
    }

    if (firstCallback && setCPUs)
    {
#if defined(SYS_gettid)
        // Set the audio callback to CPU 0 if requested. This handles case of low-latency mode
        cpu_set_t pumpthread_set;      
        pid_t audioCallbackPID;
        INT32 ret; 
        static const int cpuToSet = 0;
        CPU_ZERO(&pumpthread_set);
        CPU_SET(cpuToSet, &pumpthread_set);         
        audioCallbackPID = gettid();
        ret = sched_setaffinity(audioCallbackPID, sizeof(cpu_set_t), &pumpthread_set);  
        if (ret == 0)
        {
            printf("Set audio callback thread (PID = %d) to CPU %d \n", audioCallbackPID, cpuToSet); 
        }
        else 
        {
            printf("Could not set CPU affinity of audo callback thread (PID = %d) to CPU %d , errno: %d \n", audioCallbackPID, cpuToSet, errno);
        }
#endif
        firstCallback = 0;
    }

    if (aweOS_layoutIsValid(g_AWEOSInstance))
    {
        if (aweOS_audioIsStarted(g_AWEOSInstance))
        {
            INT32 ret;
            // import samples for each input device channel 
            for (i = 0; i < hwInputChannels; i++)
            {
                ret = aweOS_audioImportSamples(g_AWEOSInstance, ((UINT32*)inputBuffer) +  i, hwInputChannels, i, Sample32bit); 
                if (ret != 0)
                {
                    printf("error import samples: error: %d -- %s\n", ret, aweOS_errorToString(ret));
                }
            }

            // pump the audio through the loaded layout
            pumpRet = aweOS_audioPumpAll(g_AWEOSInstance);  
            if (pumpRet < 0)
            {
                // Ignore E_CALLBACK_NOT_REALTIME error
                if (pumpRet != E_CALLBACK_NOT_REALTIME)
                {
                    printf("pumpAll failing with error code: %d -- %s\n", pumpRet, aweOS_errorToString(pumpRet));
                }
            }

            // export latest samples for each device output channel 
            for (j = 0; j < hwOutputChannels; j++)
            {
                ret = aweOS_audioExportSamples(g_AWEOSInstance, ((UINT32*)outputBuffer) +  j, hwOutputChannels, j, Sample32bit); 
                if (ret != 0)
                {
                    printf("Failed on export samples: error: %d -- %s\n", ret, aweOS_errorToString(ret));
                }               
            }           
        }
    }
    return AudioStream_CallbackResult_Continue;
}

int initializeAudioStream()
{
    as = AudioStream_create();
    if (as == NULL)
    {
        printf("Error initializing AudioStream\n");
    }
    else
    {
        printf("AudioStream init success\n");
    }

    AudioStream_open(as, inputDeviceName, hwInputChannels,
        outputDeviceName, hwOutputChannels,
        AudioStream_SampleFormat_S32_LE,
        sampleRate, fundamentalBlockSize,
        audio_callback, NULL);

    return 0;
}

static void usage(const char *program)
{
    printf(
        "Usage: %s [args]\n"
        "       -load:<file>                    AWB file to load. If no AWB is specified, then app waits for a layout from tuning interface\n"
        "       -inputdevice:<str>              The alsa device id for the input device. Default hw:0,0 \n"
        "       -outputdevice:<str>             The alsa device id for the output device. Default hw:0,0\n"
        "       -hwinchannels:<int>             The number of hw input channels for the alsa audio device \n"
        "       -hwoutchannels:<int>            The number of hw output channels for the alsa audio device\n"
        "       -blocksize:<int>                The fundamental hw blocksize of the alsa device (DMA)\n"
        "       -samplerate:<int>               The samplerate of the audio hw\n"
        "       -enabledAudioLog                Enable audio logging. Logs will be saved to <appdirectory>/audio-logs/\n"
        "       -portno:<int>                   Port number to open the TCP tuning socket on. Default 15002\n"
        "       -coreSpeed:<float>              Processor clock speed in Hz. Default 1 GHz\n"
        "       -profileSpeed:<float>           Application profiling speed in Hz. Default 10 MHz \n"
        "       -enableCPUAffinity              Enable the logic in audioStart callback which will query thread PIDs and attempt to set CPU affinity per layout \n"
        "       -help                           Show usage\n"
        "This program shows realtime audio integration with the alsa API.\n",
        program);
        exit(0);
}

int main(int argc, char **argv)
{
    // Setup signal handler 
    if (signal(SIGINT, sig_handler) == SIG_ERR)
    {
        printf("Can't catch SIGINT (%d)\n", SIGINT);
    }

    inputDeviceName = "hw:0,0";
    outputDeviceName = "hw:0,0";
    setCPUs = 0;
    UINT32 i;
    if (1 == argc)
    {
        printf("No command line options specified. Using defaults\n");

    }
    else 
    {
        for (i = 1; i < argc; i++)
        {
            const char *arg = argv[i];
            if ((0 == strncmp(arg, "-inputdevice:", 13)))
            {
                inputDeviceName = (char*)(arg + 13);
                printf(" cmd line arg set inputdevice to %s \n", inputDeviceName);
            }
            else if ((0 == strncmp(arg, "-outputdevice:", 14)))
            {
                outputDeviceName = (char*)(arg + 14);   
                printf(" cmd line arg set outputdevice to %s \n", outputDeviceName);    
            }
            else if ((0 == strncmp(arg, "-hwinchannels:", 14)))
            {
                hwInputChannels = atoi(arg + 14);   
                printf(" hardware input channels set to %u \n", hwInputChannels);   
            }
            else if ((0 == strncmp(arg, "-hwoutchannels:", 15)))
            {
                hwOutputChannels = atoi(arg + 15);  
                printf(" hardware output channels set to %u \n", hwOutputChannels); 
            }
            else if ((0 == strncmp(arg, "-blocksize:", 11)))
            {
                fundamentalBlockSize = atoi(arg + 11);  
                printf(" fundamental block size set to %u \n", fundamentalBlockSize);   
            }
            else if ((0 == strncmp(arg, "-samplerate:", 12)))
            {
                sampleRate = atof(arg + 12);    
                printf(" sample rate set to %f \n", sampleRate);    
            }
            else if ((0 == strncmp(arg, "-portno:", 8))) 
            {
                portNo = atoi(arg + 8);
                printf(" portno for socket is %u \n", portNo);
            }
            else if (0 == strncmp(arg, "-load:", 6))
            {
                awbPath = arg + 6;
                loadAWB = 1;
                printf(" load:      %s\n",awbPath);     
            }   
            else if ((0 == strncmp(arg, "-enableAudioLog", 15)))
            {
                enableAudioLogging = 1;
                printf(" audio logging enabled \n");
            }   
            else if ((0 == strncmp(arg, "-coreSpeed:", 11)))
            {
                coreSpeed = atof(arg + 11);
                printf(" coreSpeed set to %f\n", coreSpeed);
            }
            else if ((0 == strncmp(arg, "-profileSpeed:", 14)))
            {
                profileSpeed = atof(arg + 14);
                printf(" profileSpeed set to %f \n", profileSpeed);
            }
            else if ((0 == strncmp(arg, "-enableCPUAffinity", 13)))
            {
                setCPUs = 1;
                printf(" enableCPUAffinity activated. query for PIDs in audiostart callback and attempt to set CPU affinity \n");
            }       
            else if ((0 == strncmp(arg, "-help", 5)))
            {
                usage(argv[0]);
            }       
            else
            {
                printf("main: unknown option '%s'\n", arg);
            }
        }       
    }

    aweOS_getParamDefaults(&configParams); 
    configParams.inChannels = hwInputChannels; 
    configParams.outChannels = hwOutputChannels;
    configParams.sampleRate = sampleRate;
    configParams.fundamentalBlockSize = fundamentalBlockSize; 
    configParams.numThreads = 8;
    configParams.cbAudioStart = audioStartCallback;
    configParams.cbAudioStop = audioStopCallback;
    configParams.instanceId = 0;
    configParams.coreSpeed = coreSpeed;
    configParams.profileSpeed = profileSpeed;

    // Larger packet buffer size can improve tuning interface speed
    configParams.packetBufferSize = 4105;

    INT32 ret;
    ret = aweOS_init(&g_AWEOSInstance, &configParams, moduleDescriptorTable, moduleDescriptorTableSize); 
    if (ret != 0) //if the initialization fails, don't do anything and exit the app
    {
        printf("FATAL: aweOS_init failed. exiting application \n");
        exit(1);
    }
    else 
    {
        initializeAudioStream(); 
        if (enableAudioLogging)
        {
            ret = aweOS_audioRecordingEnable(g_AWEOSInstance, "audio-logs", "rtaudio-alsa", 100, Sample32bit);
            if (ret != 0)
            {
                printf("failed to enable audio logging: ret = %d\n", ret);
            }
            ret = aweOS_audioRecordingRegisterNotificationCallback(g_AWEOSInstance, audioRecordNotifyCallback, AUDIO_RECORDING_NOTIFICATION_ALL);
            if (ret != 0)
            {
                printf("Failed to register audio logging callback function: ret = %d\n", ret);
            }
        }

        INT32 tuningRet = aweOS_tuningSocketOpen(&g_AWEOSInstance, portNo, 1); 
        if (tuningRet < 0)
        {
            printf("Failing opening tuning interface with error %s \n", aweOS_errorToString(tuningRet));
        }
        else 
        {
            printf("Opened TCP tuning interface on port %u: Waiting for AWE Server Connection from PC... \n", portNo);
        }

        if (loadAWB)
        {
            loadAWBFile(g_AWEOSInstance, awbPath);
        }

        while (1)
        {
            usleep(100000);
        }
    }
}

Filename: RTAudio-alsa.c


Updated on 2026-02-10 at 15:44:55 -0500