afm_ctl.c example

Updated: April 19, 2023

This is the source for the afm_ctl utility.

Note: The afm_ctl utility is shipped with Acoustic Management Platform 3.0.

For information about using this utility, see afm_ctl in the QNX Neutrino Utilities Reference.

/*
 * Copyright 2016, QNX Software Systems Ltd. All Rights Reserved.
 *
 * This source code may contain confidential information of QNX Software
 * Systems Ltd.  (QSSL) and its licensors. Any use, reproduction,
 * modification, disclosure, distribution or transfer of this software,
 * or any software which includes or is based upon any of this code, is
 * prohibited unless expressly authorized by QSSL by written agreement. For
 * more information (including whether this source code file has been
 * published) please email licensing@qnx.com.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/asoundlib.h>
#include <string.h>
#include <devctl.h>
#include <errno.h>
#include <ctype.h>
#include <ioctl.h>


//*****************************************************************************
/* *INDENT-OFF* */
#ifdef __USAGE
%C [Options]

Options:
    -a [card#:]<dev#>        the AFM card & device number to start/issue command
                             OR
    -a [name]                the AFM card name (e.g. voice, icc) to start/issue command

    -f <filename>            set wav file (full path) (recorder/player AFMs only)
    -m <mode>                set audio mode
    -c                       reset audio mode
    -t <dataset>             load runtime acoustic processing dataset
    -u                       clear runtime acoustic processing dataset
    -l <ms_offset>           start microphone latency test
    -r <ms_offset>           start reference latency test
    -v <rpm>                 set rpm VIN value
                             (diagnostic use only - only applicable if rpm is a base VIN)
    -x <param_id:chn>[:data[,data]]
                             calls set/get data on afm for acoustic processing parameters of size int16_t
                             up to 10 comma separated data values can be set
    -y <param_id:chn>[:data[,data]]
                             calls set/get data on afm for acoustic processing parameters of size int32_t
                             up to 10 comma separated data values can be set
    -z <param_id>[:data]     calls set/get data on afm for afm parameters of size int32_t
    -s                       stop AFM
    -i                       display AFM info
#endif
/* *INDENT-ON* */
//*****************************************************************************

const char* optstring = "a:f:m:sl:r:cv:t:ux:y:z:i";
#define LATENCY_TEST_MIC 0
#define LATENCY_TEST_REF 1

static snd_afm_t* setup_afm_handle(char* arg)
{
    int rtn;
    int dev = 0, card = 0;
    const char* name = NULL;
    snd_afm_t* afm_handle = NULL;
    char *tmp = strchr(arg, ':');

    if (tmp)
    {
        card = atoi(arg);
        dev = atoi(tmp + 1);
    }
    else if (isalpha(arg[0]) || (arg[0] == '/'))
        name = arg;
    else
        dev = atoi(arg);

    if (name)
    {
        printf("Using %s\n", name);
        rtn = snd_afm_open_name(&afm_handle, name);
    }
    else
    {
        printf("Using card %d device %d \n", card, dev);
        rtn = snd_afm_open(&afm_handle, card, dev);
    }
    if (rtn != EOK)
    {
        fprintf(stderr, "snd_afm_open failed: (%d) %s\n", rtn, snd_strerror(rtn));
        return NULL;
    }

    return afm_handle;
}

static inline char* afm_state_str(int state) {
    switch (state) {
        case SND_AFM_STATE_IDLE:        return "SND_AFM_STATE_IDLE";
        case SND_AFM_STATE_RUNNING:     return "SND_AFM_STATE_RUNNING";
        case SND_AFM_STATE_RUNNING_PCM: return "SND_AFM_STATE_RUNNING_PCM";
        case SND_AFM_STATE_SHUTDOWN:    return "SND_AFM_STATE_SHUTDOWN";
        default:                        return "Unknown";
    }
}

static int display_info(snd_afm_t* const afm_handle)
{
    int rtn = EOK;
    snd_afm_info_t info;
    snd_afm_status_t status;

    if ((rtn = snd_afm_info(afm_handle, &info)) == EOK) {
        printf("name:      %s\n", info.name);
        printf("device:    afmC%dD%d\n", info.card, info.device);
        printf("cardname:  %s\n", info.cardname);
        if ((rtn = snd_afm_status(afm_handle, &status)) == EOK) {
            printf("state:     %s\n", afm_state_str(status.state));
            printf("timestamp: %" PRId64 "\n", status.ms_processed);
        } else {
            fprintf(stderr, "Failed to retreive AFM status: (%d) %s\n", rtn, snd_strerror(rtn));
        }
    } else {
        fprintf(stderr, "Failed to retreive AFM info: (%d) %s\n", rtn, snd_strerror(rtn));
    }
    return rtn;
}

static int latency_test(snd_afm_t* const afm_handle, int input_device, int ms_offset)
{
    int rtn = EOK;
    int fd;
    snd_afm_latency_test_t test;

    if ((fd = snd_afm_file_descriptor(afm_handle)) > 0)
    {
        test.input_device = input_device;  /* 0 = mic, 1 = ref */
        test.input_voice = 0;
        test.ms_offset = ms_offset;

        if (ioctl(fd, SND_AFM_IOCTL_START_LATENCY_TEST, &test) < 0) {
            fprintf(stderr, "Failed to start latency test: (%d) %s\n", errno, strerror(errno));
            rtn = -errno; /* Negate errno to match snd_xxx error codes */
        }
    }
    else
    {
        fprintf(stderr, "Failed to get AFM descriptor for latency test (%d) %s\n", errno, strerror(errno));
        rtn = -errno; /* Negate errno to match snd_xxx error codes */
    }
    return rtn;
}

static int set_audio_mode(snd_afm_t* afm_handle, const char* mode)
{
    int rtn;
    char str[64];

    if (mode[0]) {
        printf("Setting mode to %s\n", mode);
    } else {
        printf("Clearing mode\n");
    }
    if ((rtn = snd_afm_set_audio_mode(afm_handle, mode)) != EOK)
        fprintf(stderr, "Failed to set mode: (%d) %s\n", rtn, snd_strerror(rtn));

    if ((rtn = snd_afm_get_audio_mode(afm_handle, str, sizeof(str))) != EOK)
        fprintf(stderr, "Failed to get mode: (%d) %s\n", rtn, snd_strerror(rtn));
    else
        printf("Audio Mode = %s\n", str);
    return rtn;
}

static int set_dataset(snd_afm_t* afm_handle, const char* dataset)
{
    int rtn;
    int ap_status = 0;

    if (dataset[0])
        printf("Loading dataset %s\n", dataset);
     else
        printf("Clearing dataset\n");

    if ((rtn = snd_afm_load_ap_dataset(afm_handle, dataset, &ap_status)) != EOK)
        fprintf(stderr, "Failed to set dataset: (%d) %s\n", rtn, snd_strerror(rtn));
    if (ap_status != 0)
        printf("Acoustic processing returned status=0x%04X\n", ap_status);
    return rtn;
}

static int set_rpm_vin(snd_afm_t* afm_handle, int rpm)
{
    int rtn;
    int vinCount = 0;
    if ((rtn = snd_afm_get_vin_list_count(afm_handle, &vinCount)) == EOK) {
        snd_afm_vin_list_item_t* vin_items = alloca( sizeof(snd_afm_vin_list_item_t) * vinCount);
        snd_afm_vin_pair_t* vin_pairs = alloca( sizeof(snd_afm_vin_pair_t) * vinCount);
        if (vin_items && vin_pairs)
        {
            memset(vin_pairs, 0, sizeof(snd_afm_vin_pair_t) * vinCount);
            if ((rtn = snd_afm_get_vin_list(afm_handle, vin_items, vinCount)) == EOK) {
                int i;
                for (i=0; i<vinCount; i++) {
                    vin_pairs[i].key = vin_items[i].key;
                    if (vin_items[i].is_rpm) {
                        vin_pairs[i].value = rpm;
                        printf("Vin 0x%X set to %d\n", vin_pairs[i].key,  vin_pairs[i].value);
                    }
                }
                rtn = snd_afm_set_vin_stream(afm_handle, vin_pairs, vinCount);
            }
        } else {
            rtn = -errno; /* Negate errno to match snd_xxx error codes */
        }
    }
    if (rtn != EOK)
    {
        fprintf(stderr, "Failed to set RPM (%d) %s\n", rtn, snd_strerror(rtn));
    }
    return rtn;
}

static void printout_data(void* data, int count, uint32_t data_size)
{
    int i;

    if (data_size == sizeof(int16_t)) {
        int16_t* data16 = (int16_t*)data;
        printf("%d", data16[0]);
        if (data16[0] %lt; 0) {
            printf("(%uU)", (uint16_t)data16[0]);
        }
        for (i=1; i<count; i++) {
            printf(",%d", data16[i]);
            if (data16[i] < 0) {
                printf("(%uU)", (uint16_t)data16[i]);
            }
        }
    } else {
        int32_t* data32 = (int32_t*)data;
        printf("%d", data32[0]);
        if (data32[0] < 0) {
            printf("(%uU)", (uint32_t)data32[0]);
        }
        for (i=1; i<count; i++) {
            printf(",%d", data32[i]);
            if (data32[i] < 0) {
                printf("(%uU)", (uint32_t)data32[i]);
            }
        }
    }
}

/* Set/get acoustic processing parameter */
#define MAX_VALS 10
static int set_ap_data(snd_afm_t* afm_handle, char* arg, uint32_t data_size)
{
    int set_data = 0, rtn;
    int32_t data[MAX_VALS] = {0};
    snd_afm_ap_param_t param = {
        .size = data_size,
    };
    int count = 0;
    int32_t* data32 = (int32_t*)data;
    int16_t* data16 = (int16_t*)data;
    char *tokStr, *val;

    param.dataId = strtol(arg, &arg, 0);
    if (errno == ERANGE || errno == EINVAL) {
        fprintf(stderr, "Invalid ap data id\n");
        return 1;
    }
    if (*arg) arg++;
    param.channel = strtol(arg, &arg, 0);
    if (errno == ERANGE || errno == EINVAL) {
        fprintf(stderr, "Invalid channel\n");
        return 1;
    }
    if (*arg) arg++;
    /* Scan string for data */
    tokStr = strdup(arg);
    if (tokStr != NULL)
    {
        val = strtok(tokStr, ",");
        while (val != NULL) {
            if (count == MAX_VALS) {
                fprintf(stderr, "Too many values\n");
                free(tokStr);
                return 1;
            }
            if (data_size == sizeof(int16_t)) {
                data16[count] = strtol(val, NULL, 0);
            } else {
                data32[count] = strtol(val, NULL, 0);
            }
            if (errno != ERANGE && errno != EINVAL) {
                count++;
                val = strtok(NULL, ",");
            } else {
                /* Fallback to get_data on error */
                count = 0;
                val = NULL;
            }
        }
        set_data = (count != 0);
        free(tokStr);
    }
    if (set_data) {
        param.size = count * data_size;
        printf("AFM set_ap_data id=0x%04x data=", param.dataId);
        printout_data(data, count, data_size);
        rtn = snd_afm_set_ap_data(afm_handle, &param, data);
    } else {
        /* Provide the entire buffer for the get data result */
        param.size = sizeof(data);
        printf("AFM get_ap_data id=0x%04x", param.dataId);
        rtn = snd_afm_get_ap_data(afm_handle, &param, data);
        count = param.size / data_size;
    }
    printf(" ret=%d ap_ret=%d data_ret=", rtn, param.status);
    printout_data(data, count, data_size);
    printf(" -- %s\n", param.status ? "AP Error" : strerror(-rtn));
    return rtn;
}


/* Set/get acoustic processing parameter */
static int set_afm_param(snd_afm_t* afm_handle, char* arg, uint32_t data_size)
{
    int set_data, rtn;
    int32_t data, param_id;

    param_id = strtol(arg, &arg, 0);
    if (errno == ERANGE || errno == EINVAL) {
        fprintf(stderr, "Invalid afm param data id\n");
        return 1;
    }
    if (*arg) arg++;
    data = strtol(arg, &arg, 0);
    set_data = (errno != ERANGE && errno != EINVAL);
    errno = EOK;
    if (set_data) {
        printf("AFM set_param id=0x%04x data=%d ", param_id, data);
        rtn = snd_afm_set_param(afm_handle, param_id, data_size, &data);
    } else {
        printf("AFM get_param id=0x%04x ", param_id);
        rtn = snd_afm_get_param(afm_handle, param_id, &data_size, &data);
    }
    printf("ret=%d data_ret=%d -- %s\n", rtn, data, strerror(-rtn));
    return rtn;
}


int main(int argc, char *argv[])
{
    int rtn = EOK;
    snd_afm_t *afm_handle = NULL;
    int start_afm = 1;
    int c;

    /* first setup afm handle */
    while ((c = getopt(argc, argv, optstring)) != EOF)
    {
        switch (c)
        {
        case 'a':
            if (afm_handle) {
                fprintf(stderr, "Multiple AFMs specified, use one at a time\n");
                snd_afm_close(afm_handle);
                return EXIT_FAILURE;
            }
            if (!(afm_handle = setup_afm_handle(optarg))) {
                return EXIT_FAILURE;
            }
            break;
        default:
            /* handle other options instead of starting AFM */
            start_afm = 0;
            break;
        }
    }

    if (!afm_handle) {
        fprintf(stderr, "No AFM specified, use -a option\n");
        return EXIT_FAILURE;
    }
    if (start_afm) {
        printf("Starting AFM \n");
        if ((rtn = snd_afm_start(afm_handle)) != EOK)
            fprintf(stderr, "Failed to start AFM: (%d) %s\n", rtn, snd_strerror(rtn));

        snd_afm_close(afm_handle);
        return (rtn == EOK) ? EXIT_SUCCESS : EXIT_FAILURE;
    }

    /* reset optind and handle other options */
    optind = 1;
    while (((c = getopt(argc, argv, optstring)) != EOF) && (rtn == EOK))
    {
        switch (c)
        {
        case 'a':
            break;

        case 's':
            printf("Stopping AFM \n");
            if ((rtn = snd_afm_stop(afm_handle)) != EOK)
                fprintf(stderr, "Failed to stop AFM: (%d) %s\n", rtn, snd_strerror(rtn));
            break;

        case 'l':
            rtn = latency_test(afm_handle, LATENCY_TEST_MIC, atoi(optarg));
            break;

        case 'r':
            rtn = latency_test(afm_handle, LATENCY_TEST_REF, atoi(optarg));
            break;

        case 'f':
            printf("Setting filename %s, len = %zu\n", optarg, strlen(optarg));
            if ((rtn = snd_afm_set_path(afm_handle, SND_AFM_WAV_FILE, optarg)) != EOK)
                fprintf(stderr, "Failed to set filename: (%d) %s\n", rtn, snd_strerror(rtn));
            break;

        case 'm':
            rtn = set_audio_mode(afm_handle, optarg);
            break;

        case 'c':
            rtn = set_audio_mode(afm_handle, "");
            break;

        case 't':
            rtn = set_dataset(afm_handle, optarg);
            break;

        case 'u':
            rtn = set_dataset(afm_handle, "");
            break;

        case 'i':
            rtn = display_info(afm_handle);
            break;

        case 'v':
            rtn = set_rpm_vin(afm_handle, strtoul(optarg, NULL, 0));
            break;

        case 'x':
            rtn = set_ap_data(afm_handle, optarg, sizeof(uint16_t));
            break;

        case 'y':
            rtn = set_ap_data(afm_handle, optarg, sizeof(uint32_t));
            break;

        case 'z':
            rtn = set_afm_param(afm_handle, optarg, sizeof(uint32_t));
            break;

        default:
            fprintf(stderr, "Invalid option '%c'\n", c);
            rtn = -1;
            break;
        }
    }

    snd_afm_close(afm_handle);

    return (rtn == EOK) ? EXIT_SUCCESS : EXIT_FAILURE;
}

#if defined(__QNXNTO__) && defined(__USESRCVERSION)
#include <sys/srcversion.h>
__SRCVERSION("$URL$ $Rev$")
#endif