snsr-eval.c¶
This is the source code for the snsr-eval command-line tool.
The sample build also creates snsr-eval-subset from the same source. This is a version of snsr-eval that includes only the code modules required to run spot-hbg-enUS-1.4.0-m.snsr. It's built with -DSNSR_USE_SUBSET and snsr-custom-init.c created by snsr-edit. See src/CMakeLists.txt or Makefile for details.
Instructions¶
See snsr-eval.
Code¶
Available in this TrulyNatural SDK installation at ~/Sensory/TrulyNaturalSDK/7.6.1/sample/c/src/snsr-eval.c
snsr-eval.c
/* Sensory Confidential
*
* Copyright (C)2016-2025 Sensory, Inc. https://sensory.com/
*
* TrulyHandsfree SDK model evaluation command-line utility.
*------------------------------------------------------------------------------
* This utility supports evaluation of many of the TrulyHandsfree/TrulyNatural
* SDK task types. The source code is provided as a detailed example.
*
* For most use keyword spotting implementations the live-spot.c sample is a
* better starting point.
*------------------------------------------------------------------------------
*/
#ifdef _WIN32
# include <windows.h>
#endif
#include <snsr.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TASKS_SUPPORTED\
SNSR_PHRASESPOT " ~0.5.0 || 1.0.0;"\
SNSR_PHRASESPOT_VAD " ~0.5.0 || 1.0.0;"\
SNSR_LVCSR " 1.0.0;"\
SNSR_VAD " 1.0.0;" \
SNSR_FEATURE " 1.0.0;" \
SNSR_FEATURE_PHRASESPOT " 1.0.0;"\
SNSR_FEATURE_LVCSR " 1.0.0;"\
SNSR_FEATURE_VAD " 1.0.0"
#define DEFAULT_SAMPLE_RATE 16000
#define DEFAULT_FRAME_SIZE_MS 15
#if defined(_MSC_VER) && (_MSC_VER < 1900)
# define snprintf _snprintf
#endif
typedef struct {
int nBest; /* Requested N-best results, usually 1 */
int verbose; /* Amount of detail resultEvent prints */
unsigned isPartial:1; /* 1 if this is a preliminary result */
unsigned isPhrase:1; /* 1 if this is a phrase-level iteration */
} ResultConfig;
typedef struct {
char *filename; /* partial output filename, e.g. out/ */
size_t prefix; /* filename directory path length */
size_t length; /* size of the filename buffer */
int verbose;
} VadContext;
static SnsrRC
showAlignment(SnsrSession s, const char *key, void *privateData)
{
SnsrRC r;
ResultConfig *config = (ResultConfig *)privateData;
const char *phrase;
const char *partial = config->isPartial? "P ": "";
double begin, end, score = -1.0, svscore;
snsrGetString(s, SNSR_RES_TEXT, &phrase);
snsrGetDouble(s, SNSR_RES_BEGIN_MS, &begin);
snsrGetDouble(s, SNSR_RES_END_MS, &end);
r = snsrGetDouble(s, SNSR_RES_SCORE, &score);
if (r == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
r = snsrGetDouble(s, SNSR_RES_SV_SCORE, &svscore);
if (r != SNSR_RC_OK) return r;
if (config->nBest > 1 && config->isPhrase && !config->isPartial) {
int count = 1, index = 0;
snsrGetInt(s, SNSR_RES_COUNT, &count);
snsrGetInt(s, SNSR_RES_INDEX, &index);
printf("%2i/%i %6.0f %6.0f %s\n", index + 1, count, begin, end, phrase);
} else if (config->verbose <= -2) {
if (!config->isPartial) printf("%s\n", phrase);
} else if (config->verbose == -1) {
printf("%s%20s", phrase, config->isPartial? "\r": "\n");
} else if (config->verbose == 0) {
printf("%s%6.0f %6.0f %s\n", partial, begin, end, phrase);
} else {
printf("%s%6.0f %6.0f (%.4g%s) %s\n",
partial, begin, end,
score >= 0? score: svscore,
score >= 0? "": " sv",
phrase);
}
fflush(stdout);
return r;
}
static SnsrRC
showAvailablePoint(SnsrSession s, const char *key, void *privateData)
{
SnsrRC r;
int point, *first = (int *)privateData;
r = snsrGetInt(s, SNSR_RES_AVAILABLE_POINT, &point);
if (r == SNSR_RC_OK) {
if (*first) printf("Available operating points: %i", point);
else printf(", %i", point);
*first = 0;
}
return r;
}
static SnsrRC
showVocab(SnsrSession s, const char *key, void *privateData)
{
SnsrRC r;
const char *text = NULL;
int id = -1, *first = (int *)privateData;
snsrGetInt(s, SNSR_RES_ID, &id);
r = snsrGetString(s, SNSR_RES_TEXT, &text);
if (r != SNSR_RC_OK) return r;
if (*first) printf("Available vocabulary:\n");
printf(" %2i: \"%s\"\n", id, text);
*first = 0;
return r;
}
static SnsrRC
entityIterator(SnsrSession s, const char *key, void *privateData)
{
const char *entity, *value;
double score = 0;
snsrGetDouble(s, SNSR_RES_NLU_ENTITY_SCORE, &score);
snsrClearRC(s); /* Not all models support NLU scores, ignore errors */
snsrGetString(s, SNSR_RES_NLU_ENTITY_NAME, &entity);
snsrGetString(s, SNSR_RES_NLU_ENTITY_VALUE, &value);
printf("NLU entity: %s (%.4g) = %s\n", entity, score, value);
return snsrRC(s);
}
static SnsrRC
intentEvent(SnsrSession s, const char *key, void *privateData)
{
const char *intent, *value;
double score = 0;
snsrGetDouble(s, SNSR_RES_NLU_INTENT_SCORE, &score);
snsrClearRC(s); /* Not all models support NLU scores, ignore errors */
snsrGetString(s, SNSR_RES_NLU_INTENT_NAME, &intent);
snsrGetString(s, SNSR_RES_NLU_INTENT_VALUE, &value);
printf("NLU intent: %s (%.4g) = %s\n", intent, score, value);
return snsrForEach(s, SNSR_NLU_ENTITY_LIST,
snsrCallback(entityIterator, NULL, privateData));
}
static SnsrRC
nluEvent(SnsrSession s, const char *key, void *privateData)
{
SnsrRC r;
const char *name, *parentPath = (char *)privateData, *value;
char *path;
double score = 0;
size_t pathLen = 0;
int nluMax = 1, nBest = 1;
snsrGetDouble(s, SNSR_RES_NLU_SLOT_SCORE, &score);
snsrClearRC(s); /* Not all models support NLU scores, ignore errors */
snsrGetString(s, SNSR_RES_NLU_SLOT_NAME, &name);
r = snsrGetString(s, SNSR_RES_NLU_SLOT_VALUE, &value);
if (r != SNSR_RC_OK) return r;
pathLen = strlen(SNSR_RES_NLU_SLOT_VALUE) + strlen(name) + 1;
if (parentPath) pathLen += strlen(parentPath) + 1;
path = malloc(pathLen);
if (!path) return SNSR_RC_NO_MEMORY;
if (!parentPath) {
strcpy(path, SNSR_RES_NLU_SLOT_VALUE);
strcat(path, name);
} else {
strcpy(path, parentPath);
strcat(path, ".");
strcat(path, name);
}
/* SNSR_NLU_RES_MAX introduced in 6.16.0, missing from older models */
r = snsrGetInt(s, SNSR_NLU_RES_MAX, &nluMax);
if (r != SNSR_RC_OK) snsrClearRC(s);
/* SNSR_RESULT_MAX introduced in 6.17.0, missing from older models */
r = snsrGetInt(s, SNSR_RESULT_MAX, &nBest);
if (r != SNSR_RC_OK) snsrClearRC(s);
if (nBest > 1 || nluMax > 1) {
int nluIndex = 0, nluCount = 1, recIndex = 0, recCount = 0;
snsrGetInt(s, SNSR_RES_COUNT, &recCount);
snsrGetInt(s, SNSR_RES_INDEX, &recIndex);
snsrClearRC(s); /* SNSR_RES_{COUNT,INDEX} not available before 6.17.0 */
snsrGetInt(s, SNSR_RES_NLU_COUNT, &nluCount);
snsrGetInt(s, SNSR_RES_NLU_INDEX, &nluIndex);
if (recCount > 1) {
printf("%2i/%i NLU %2i/%i %s (%.4g) = %s\n", recIndex + 1, recCount,
nluIndex + 1, nluCount, path, score, value);
} else {
printf("NLU %2i/%i %s (%.4g) = %s\n",
nluIndex + 1, nluCount, path, score, value);
}
} else {
printf("NLU %s (%.4g) = %s\n", path, score, value);
}
r = snsrForEach(s, SNSR_NLU_SLOT_LIST, snsrCallback(nluEvent, NULL, path));
free(path);
return r;
}
static SnsrRC
resultEvent(SnsrSession s, const char *key, void *privateData)
{
SnsrCallback c;
ResultConfig *config = (ResultConfig *)privateData;
const char *partial = config->isPartial? "P ": "";
/* Skip empty (LVCSR) results. */
if (config->nBest > 1) {
const char *phrase = NULL;
snsrGetString(s, SNSR_RES_TEXT, &phrase);
if (phrase && !phrase[0]) return snsrRC(s);
}
if (config->verbose > 0 && !config->isPartial) {
const char *domain = NULL;
SnsrRC r = snsrGetString(s, SNSR_RES_DOMAIN, &domain);
if (r != SNSR_RC_OK) snsrClearRC(s);
else if (domain) printf("domain: %s\n", domain);
}
c = snsrCallback(showAlignment, NULL, privateData);
snsrRetain(c);
if (config->verbose > 1) printf("%sphrase:\n", partial);
config->isPhrase = 1;
snsrForEach(s, SNSR_PHRASE_LIST, c);
config->isPhrase = 0;
if (config->verbose > 1) {
printf("%swords:\n", partial);
snsrForEach(s, SNSR_WORD_LIST, c);
}
if (config->verbose > 2) {
printf("%sphonemes:\n", partial);
snsrForEach(s, SNSR_PHONE_LIST, c);
}
if (config->verbose > 1) {
printf("\n");
fflush(stdout);
}
snsrRelease(c);
return snsrRC(s);
}
/* The SNSR_ADAPT_STARTED_EVENT is called from a worker thread with
* the SnsrSession argument set to NULL.
*/
static SnsrRC
adaptStartedEvent(SnsrSession s, const char *key, void *privateData)
{
printf(" [%s] on worker thread\n", key);
fflush(stdout);
return SNSR_RC_OK;
}
/* Display events with sample time-stamps */
static SnsrRC
showEvent(SnsrSession s, const char *key, void *privateData)
{
SnsrRC r;
double samples, timestamp = 0;
int rate = DEFAULT_SAMPLE_RATE;
r = snsrGetInt(s, SNSR_SAMPLE_RATE, &rate);
/* VAD task types do not include SNSR_SAMPLE_RATE support, use default */
if (r == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
r = snsrGetDouble(s, SNSR_RES_SAMPLES, &samples);
if (r == SNSR_RC_OK) {
timestamp = samples / rate * 1000;
} else if (r == SNSR_RC_SETTING_NOT_FOUND) {
double frames = 0;
snsrClearRC(s);
r = snsrGetDouble(s, SNSR_RES_FRAMES, &frames);
timestamp = frames * DEFAULT_FRAME_SIZE_MS;
}
if (privateData) {
const char *user = "(unknown)";
snsrGetString(s, SNSR_USER, &user);
printf("%6.0f [%s] %s\n", timestamp, key, user);
} else {
printf("%6.0f [%s]\n", timestamp, key);
}
fflush(stdout);
return snsrRC(s);
}
/* VAD start point detected */
static SnsrRC
vadBeginEvent(SnsrSession s, const char *key, void *privateData)
{
VadContext *c = (VadContext *)privateData;
if (c->verbose > 1) showEvent(s, key, NULL);
if (c->filename) {
SnsrStream out;
double begin = 0;
snsrGetDouble(s, SNSR_RES_BEGIN_MS, &begin);
snprintf(c->filename + c->prefix, c->length - c->prefix, "%.0f.wav", begin);
out = snsrStreamFromAudioFile(c->filename, "w", SNSR_ST_AF_DEFAULT);
snsrSetStream(s, SNSR_SINK_AUDIO_PCM, out);
snsrSetInt(s, SNSR_PASS_THROUGH, 1);
if (c->verbose > 0) {
printf("Saving VAD audio to \"%s\".\n", c->filename);
fflush(stdout);
}
}
return snsrRC(s);
}
/* VAD silence detected */
static SnsrRC
vadSilenceEvent(SnsrSession s, const char *key, void *privateData)
{
VadContext *c = (VadContext *)privateData;
if (c->verbose > 1) showEvent(s, key, NULL);
return snsrRC(s);
}
/* Vad endpoint event */
static SnsrRC
vadEndEvent(SnsrSession s, const char *key, void *privateData)
{
double begin, end;
VadContext *c = (VadContext *)privateData;
if (c->verbose > 0) {
snsrGetDouble(s, SNSR_RES_BEGIN_MS, &begin);
snsrGetDouble(s, SNSR_RES_END_MS, &end);
printf("%6.0f %6.0f [%s] VAD speech region.\n", begin, end, key);
fflush(stdout);
}
return snsrRC(s);
}
/* Optional SLM processing is about to start.
*/
static SnsrRC
slmStartEvent(SnsrSession s, const char *key, void *privateData)
{
printf("SLM: ");
fflush(stdout);
return SNSR_RC_OK;
}
/* SLM partial result event, SNSR_RES_TEXT is the current next word prediction
*/
static SnsrRC
slmPartialResultEvent(SnsrSession s, const char *key, void *privateData)
{
const char *txt = NULL;
SnsrRC r = snsrGetString(s, SNSR_RES_TEXT, &txt);
if (r != SNSR_RC_OK) return r;
printf("%s", txt);
fflush(stdout);
return SNSR_RC_OK;
}
/* SLM final result event, SNSR_RES_TEXT is the entire result
*/
static SnsrRC
slmResultEvent(SnsrSession s, const char *key, void *privateData)
{
printf("\n");
fflush(stdout);
return SNSR_RC_OK;
}
static void
fatal(SnsrSession s, int rc, const char *format, ...)
{
va_list a;
fprintf(stderr, "ERROR: ");
va_start(a, format);
vfprintf(stderr, format, a);
va_end(a);
fprintf(stderr, "\n");
snsrRelease(s);
exit(rc);
}
static void
fatalSession(SnsrSession s)
{
fatal(s, snsrRC(s), "%s", snsrErrorDetail(s));
}
static const char *usageDetail =
"Use a filename of - to read headerless linear 16-bit PCM little-endian \n"
"audio from stdin. If you don't specify any wave files, snsr-eval uses\n"
"live audio captured from the default audio device.\n"
"\n"
"The -d and -o options are mutually exclusive. The output directory\n"
"must be writable. Audio files created by VAD segmentation are named\n"
" <directory>/<start-time-in-ms>.wav\n"
"\n"
"Settings are strings used as keys to query or change task behavior.\n"
"Most frequently used are operating-point for wake words and command sets,\n"
"leading-silence and trailing-silence for VAD templates,\n"
"partial-result-interval for LVCSR and STT, and stt-profile for STT models.\n"
"Refer to the " SNSR_NAME " SDK documentation for a complete list and\n"
"descriptions of all supported settings.\n";
static void
usage(const char *name)
{
SnsrSession s;
const char *libInfo;
fprintf(stderr, "Evaluates/runs " SNSR_NAME " SDK .snsr model files.\n\n");
fprintf(stderr,
"usage: %s -t task [options] [wavefile ...]\n"
" options:\n"
" -a : Add tpl-vad-lvcsr to LVCSR and STT models\n"
" -d directory : VAD audio output directory\n"
" -f setting filename : load filename into task setting\n"
" -g setting value : load string into task setting\n"
" -l [-l [-l]] : reduce verbosity\n"
" -o out : VAD audio output filename\n"
" -p [-p] : Enable pipeline profiling (experimental)\n"
" -q setting : query a task setting\n"
" -s setting=value : override a task setting\n"
" -t task : specify task filename (required)\n"
" -u filename : remove unused settings and save model"
" to filename\n"
" -v [-v [-v]] : increase verbosity\n", name);
fprintf(stderr, "\n%s", usageDetail);
snsrNew(&s);
snsrGetString(s, SNSR_LIBRARY_INFO, &libInfo);
fprintf(stderr, "\n%s\n", libInfo);
snsrRelease(s);
exit(199);
}
/* Report command-line argument errors.
*/
static void
quitOnError(SnsrSession s)
{
SnsrRC r = snsrRC(s);
if (r == SNSR_RC_NO_MODEL) fatal(s, r, "set -t task before -f or -s options");
if (r != SNSR_RC_OK) fatalSession(s);
}
/* Report model license keys.
*/
static void
reportModelLicense(SnsrSession s, const char *modelfile, int verbose)
{
const char *msg = NULL;
if (verbose > 1) {
snsrGetString(s, SNSR_MODEL_LICENSE_EXPIRES, &msg);
if (msg)
fprintf(stderr, "\"%s\": %s.\n", modelfile, msg);
}
msg = NULL;
snsrGetString(s, SNSR_MODEL_LICENSE_WARNING, &msg);
if (msg)
fprintf(stderr, "WARNING for model \"%s\": %s.\n", modelfile, msg);
}
/* Show CPU required to run the model in real time. This uses timing
* information gathered while running model inference.
*/
static void
showRealTimeFactor(SnsrSession s)
{
double cpuSeconds, rtf, samplesProcessed;
int sampleRate;
SnsrRC r;
snsrClearRC(s);
snsrGetDouble(s, SNSR_RES_CPU_SECONDS_USED, &cpuSeconds);
snsrGetDouble(s, SNSR_RES_SAMPLES, &samplesProcessed);
r = snsrGetInt(s, SNSR_SAMPLE_RATE, &sampleRate);
if (r != SNSR_RC_OK) fatalSession(s);
rtf = cpuSeconds / samplesProcessed * sampleRate;
printf("CPU required for real-time recognition: %.2f%%\n", rtf * 100);
}
int
main(int argc, char *argv[])
{
SnsrRC r;
SnsrSession s;
SnsrStream tmp, audio = NULL;
int i, o, profile = 0;
int vadAdded = 0, verbose = 0;
const char *dir = NULL, *msg = NULL, *out = NULL, *save = NULL;
extern char *optarg;
extern int optind;
ResultConfig full = {1, 0, 0, 0}, partial = {1, 0, 1, 0};
VadContext vadContext = {NULL, 0, 0};
#ifdef SNSR_USE_SECURITY_CHIP
uint32_t *securityChipComms(uint32_t *in);
snsrConfig(SNSR_CONFIG_SECURITY_CHIP, securityChipComms);
#endif
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
if (argc == 1) usage(argv[0]);
r = snsrNew(&s);
if (r != SNSR_RC_OK)
fatal(s, r, "%s", s? snsrErrorDetail(s): snsrRCMessage(r));
while ((o = getopt(argc, argv, "ad:f:g:lo:pq:s:t:u:v?")) >= 0) {
switch (o) {
case 'a': {
/* Load VAD model compiled into this executable */
extern SnsrCodeModel tpl_vad_lvcsr;
const char *taskType = "";
r = snsrGetString(s, SNSR_TASK_TYPE, &taskType);
if (r != SNSR_RC_NO_MODEL) fatal(s, r, "set the -a option before -t, "
"for example %s -at task", argv[0]);
snsrClearRC(s);
snsrLoad(s, snsrStreamFromCode(tpl_vad_lvcsr));
quitOnError(s);
vadAdded = 1;
break;
}
case 'd':
dir = optarg;
break;
case 'f':
if (optind >= argc) usage(argv[0]);
snsrSetStream(s, optarg, snsrStreamFromFileName(argv[optind++], "r"));
quitOnError(s);
reportModelLicense(s, argv[optind - 1], verbose);
break;
case 'g':
if (optind >= argc) usage(argv[0]);
snsrSetStream(s, optarg, snsrStreamFromString(argv[optind++]));
quitOnError(s);
break;
case 'l':
verbose--;
break;
case 'o':
out = optarg;
break;
case 'p':
profile++;
break;
case 'q': {
const char *strVal = NULL;
if (snsrGetString(s, optarg, &strVal) == SNSR_RC_OK) {
const char *q = strVal && strchr(strVal, ' ')? "\"": "";
printf("%s = %s%s%s\n", optarg, q, strVal, q);
}
quitOnError(s);
break;
}
case 's':
snsrSet(s, optarg);
quitOnError(s);
break;
case 't':
if (vadAdded) {
/* The tpl-vad-lvcsr expects an LVCSR or STT model in slot 0 */
snsrSetStream(s, SNSR_SLOT_0, snsrStreamFromFileName(optarg, "r"));
vadAdded = 0;
} else {
snsrLoad(s, snsrStreamFromFileName(optarg, "r"));
}
quitOnError(s);
reportModelLicense(s, optarg, verbose);
break;
case 'u':
snsrSetString(s, SNSR_PRUNE_SETTINGS, "yes");
save = optarg;
break;
case 'v': verbose++;
break;
case '?':
default: usage(argv[0]);
}
}
r = snsrRequire(s, SNSR_TASK_TYPE_AND_VERSION_LIST, TASKS_SUPPORTED);
if (r == SNSR_RC_NO_MODEL) usage(argv[0]);
else if (r != SNSR_RC_OK) fatalSession(s);
if (out && dir) fatal(s, SNSR_RC_INVALID_ARG,
"The -d and -o options are multually exclusive.\n");
/* Report application license status */
if (verbose > 1) {
snsrGetString(s, SNSR_LICENSE_EXPIRES, &msg);
if (msg) fprintf(stderr, "\"%s\": %s.\n", argv[0], msg);
}
msg = NULL;
snsrGetString(s, SNSR_LICENSE_WARNING, &msg);
if (msg) fprintf(stderr, "WARNING for \"%s\": %s.\n", argv[0], msg);
r = snsrSetStream(s, SNSR_SOURCE_AUDIO_PCM, NULL);
snsrClearRC(s);
if (r == SNSR_RC_OK) {
/* No audio files provided, use live audio from the
* default capture device
*/
if (optind == argc) {
const char *taskType = "";
snsrGetString(s, SNSR_TASK_TYPE, &taskType);
if (!strcmp(taskType, SNSR_LVCSR))
fatal(s, SNSR_RC_ERROR, "With live audio LVCSR and STT models require "
"a VAD. You can add one with the -a flag.");
audio = snsrStreamFromAudioDevice(SNSR_ST_AF_DEFAULT);
if (verbose > 0) {
printf("Using live audio from default capture device. ^C to stop.\n");
fflush(stdout);
}
} else {
/* Create stream concatenation of all the audio files */
audio = snsrStreamFromString("");
for (i = optind; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] == '\0') {
tmp = snsrStreamFromFILE(stdin, SNSR_ST_MODE_READ);
} else {
tmp = snsrStreamFromAudioFile(argv[i], "r", SNSR_ST_AF_DEFAULT);
}
audio = snsrStreamFromStreams(audio, tmp);
}
}
/* Wire up the audio input stream. */
snsrSetStream(s, SNSR_SOURCE_AUDIO_PCM, audio);
} else {
/* SNSR_SOURCE_AUDIO_PCM not found, try feature-stream */
r = snsrSetStream(s, SNSR_SOURCE_FEATURE, NULL);
snsrClearRC(s);
if (r == SNSR_RC_OK) {
SnsrStream feature;
feature = snsrStreamFromString("");
for (i = optind; i < argc; i++) {
SnsrStream tmp = snsrStreamFromFileName(argv[i], "r");
feature = snsrStreamFromStreams(feature, tmp);
}
r = snsrSetStream(s, SNSR_SOURCE_FEATURE, feature);
} else r = SNSR_RC_OK;
}
/* The SNSR_OPERATING_POINT setting was introduced with 5.0.0-beta.10 */
if (verbose > 1 && snsrRC(s) == SNSR_RC_OK) {
int first = 1, point = 0;
r = snsrGetInt(s, SNSR_OPERATING_POINT, &point);
if (r == SNSR_RC_SETTING_NOT_FOUND || r == SNSR_RC_VALUE_NOT_SET) {
snsrClearRC(s);
} else {
printf("Using operating point %i.\n", point);
snsrForEach(s, SNSR_OPERATING_POINT_LIST,
snsrCallback(showAvailablePoint, NULL, &first));
printf(".\n");
fflush(stdout);
}
}
/* The SNSR_VOCAB_LIST setting was introduced with 6.7.0. */
if (verbose > 1 && snsrRC(s) == SNSR_RC_OK) {
int first = 1;
snsrForEach(s, SNSR_VOCAB_LIST, snsrCallback(showVocab, NULL, &first));
snsrClearRC(s);
}
/* Wire up the optional audio output stream. */
r = snsrSetStream(s, SNSR_SINK_AUDIO_PCM, NULL);
if (r == SNSR_RC_DST_CHANNEL_NOT_FOUND) {
r = SNSR_RC_OK;
snsrClearRC(s);
} else if (out) {
r = snsrSetStream(s, SNSR_SINK_AUDIO_PCM,
snsrStreamFromAudioFile(out, "w", SNSR_ST_AF_DEFAULT));
} else {
/* No file specified, turn off VAD audio output. */
r = snsrSetInt(s, SNSR_PASS_THROUGH, 0);
}
/* Wire up the optional feature output stream. */
if (r == SNSR_RC_OK) {
r = snsrSetStream(s, SNSR_SINK_FEATURE, NULL);
if (r == SNSR_RC_DST_CHANNEL_NOT_FOUND) {
r = SNSR_RC_OK;
snsrClearRC(s);
} else if (out) {
r = snsrSetStream(s, SNSR_SINK_FEATURE, snsrStreamFromFileName(out, "w"));
} else {
/* No file specified, turn off VAD feature output. */
r = snsrSetInt(s, SNSR_PASS_THROUGH, 0);
}
}
if (r != SNSR_RC_OK) fatalSession(s);
/* SNSR_RESULT_MAX introduced in 6.17.0, missing from older models */
r = snsrGetInt(s, SNSR_RESULT_MAX, &full.nBest);
if (r != SNSR_RC_OK) snsrClearRC(s);
/* Handle recognition results. */
full.verbose = verbose;
r = snsrSetHandler(s, SNSR_RESULT_EVENT,
snsrCallback(resultEvent, NULL, &full));
/* VAD task types do not include SNSR_RESULT_EVENT support */
if (r == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
else if (r != SNSR_RC_OK) fatalSession(s);
/* Partial results might not be available, ignore handler setup errors. */
partial.verbose = verbose;
r = snsrSetHandler(s, SNSR_PARTIAL_RESULT_EVENT,
snsrCallback(resultEvent, NULL, &partial));
if (r == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
/* VAD callback handlers. These are not supported for all task types. */
vadContext.verbose = verbose;
if (dir) {
size_t dirLen = strlen(dir);
vadContext.length = dirLen + 32;
vadContext.filename = malloc(vadContext.length);
if (!vadContext.filename)
fatal(s, SNSR_RC_NO_MEMORY, "Could not allocate output filename buffer");
strcpy(vadContext.filename, dir);
if (!dirLen) strcat(vadContext.filename, "./");
else if (dir[dirLen - 1] != '/') strcat(vadContext.filename, "/");
vadContext.prefix = strlen(vadContext.filename);
}
snsrSetHandler(s, SNSR_BEGIN_EVENT,
snsrCallback(vadBeginEvent, NULL, &vadContext));
snsrSetHandler(s, SNSR_SILENCE_EVENT,
snsrCallback(vadSilenceEvent, NULL, &vadContext));
snsrSetHandler(s, SNSR_END_EVENT,
snsrCallback(vadEndEvent, NULL, &vadContext));
snsrSetHandler(s, SNSR_LIMIT_EVENT,
snsrCallback(vadEndEvent, NULL, &vadContext));
/* Ignore not-found errors for VAD handlers */
if (snsrRC(s) == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
/* Prefer NLU intent events added in TrulyNatural 7.1.0 */
if (verbose > -3) {
r = snsrSetHandler(s, SNSR_NLU_INTENT_EVENT,
snsrCallback(intentEvent, NULL, NULL));
if (r == SNSR_RC_SETTING_NOT_FOUND || verbose > 1) {
snsrClearRC(s);
/* NLU slot events were added in TrulyNatural 6.13.0. */
r = snsrSetHandler(s, SNSR_NLU_SLOT_EVENT,
snsrCallback(nluEvent, NULL, NULL));
if (r == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
}
}
/* Events introdcued by model version 0.13.0. */
if (verbose > 1) {
snsrSetHandler(s, SNSR_LISTEN_BEGIN_EVENT,
snsrCallback(showEvent, NULL, NULL));
snsrSetHandler(s, SNSR_LISTEN_END_EVENT,
snsrCallback(showEvent, NULL, NULL));
/* Treat these as optional, for compatibility with older spotter models. */
if (snsrRC(s) == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
}
/* Continuous Adaptation spotters provide additional events. */
if (verbose > 0) {
snsrSetHandler(s, SNSR_ADAPT_STARTED_EVENT,
snsrCallback(adaptStartedEvent, NULL, NULL));
snsrSetHandler(s, SNSR_ADAPTED_EVENT,
snsrCallback(showEvent, NULL, NULL));
snsrSetHandler(s, SNSR_NEW_USER_EVENT,
snsrCallback(showEvent, NULL, (void *)1));
/* Treat these as optional as only CA spotters support them */
if (snsrRC(s) == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
}
/* SLM events are optional */
if (verbose > -3) {
snsrSetHandler(s, SNSR_SLM_START_EVENT,
snsrCallback(slmStartEvent, NULL, NULL));
snsrSetHandler(s, SNSR_SLM_PARTIAL_RESULT_EVENT,
snsrCallback(slmPartialResultEvent, NULL, NULL));
snsrSetHandler(s, SNSR_SLM_RESULT_EVENT,
snsrCallback(slmResultEvent, NULL, NULL));
if (snsrRC(s) == SNSR_RC_SETTING_NOT_FOUND) snsrClearRC(s);
}
r = snsrRun(s);
if (r != SNSR_RC_OK && r != SNSR_RC_STREAM_END) fatalSession(s);
free(vadContext.filename);
if (profile == 1) showRealTimeFactor(s);
else if (profile > 1)
snsrProfile(s, snsrStreamFromFILE(stdout, SNSR_ST_MODE_WRITE));
if (save) {
snsrReset(s);
snsrSave(s, SNSR_FM_CONFIG, snsrStreamFromFileName(save, "w"));
quitOnError(s);
if (verbose > 0) printf("Model saved to \"%s\".\n", save);
}
snsrRelease(s);
snsrTearDown();
if (out && verbose > 0) printf("VAD audio saved to \"%s\".\n", out);
return 0;
}