Files
alice-backend/station/vqe/capture.c
T

99 lines
3.7 KiB
C

// Mini 2 armhf firmware ABI, verified against libyandex_evil BuildID
// d1d0f12f8f8389c0ca72672c61530161b85353c9 (Quasar 3.21).
// stdin: ALSA plug:default, 48 kHz S16_LE, four mics followed by stereo speaker feedback.
// stdout: vqe_0, 16 kHz mono S16_LE.
#include <dlfcn.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
enum { INPUT = 1536, OUTPUT = 512, MICS = 4, SPEAKERS = 2, ASR_CHANNEL = 6 };
static const char *(*error_message)(void *);
static void (*error_free)(void *);
static void check(void *error) {
if (!error) return;
fprintf(stderr, "VQE: %s\n", error_message(error));
error_free(error);
exit(1);
}
static void *symbol(void *lib, const char *name) {
void *address = dlsym(lib, name);
if (!address) {
fprintf(stderr, "VQE: %s\n", dlerror());
exit(1);
}
return address;
}
static int16_t pcm(float sample) {
if (!isfinite(sample)) return 0;
float value = sample * 32768.0f;
if (value >= 32767) return 32767;
if (value <= -32768) return -32768;
return (int16_t)value;
}
int main(void) {
void *lib = dlopen("/system/vendor/quasar/libs/libyandex_evil.so", RTLD_NOW | RTLD_LOCAL);
if (!lib) {
fprintf(stderr, "VQE: %s\n", dlerror());
return 1;
}
#define VQE(name) symbol(lib, "YandexVqeC_" #name)
error_message = VQE(Error_GetMessage);
error_free = VQE(Error_Free);
void *(*preset)(const char *, void **) = VQE(Config_CreateFromPreset);
void (*free_config)(void *) = VQE(Config_Free);
int (*input_rate)(void *) = VQE(Config_GetInputSamplingRate);
int (*output_rate)(void *) = VQE(Config_GetOutputSamplingRate);
int (*input_chunk)(void *) = VQE(Config_GetInputChunkSize);
int (*mic_count)(void *) = VQE(Config_GetMicsCount);
int (*speaker_count)(void *) = VQE(Config_GetSpeakersCount);
void *(*create)(void *, const char *, size_t, const char **, void **) = VQE(Create);
void (*destroy)(void *) = VQE(Free);
void *(*process)(void *, const float *, size_t, const float *, size_t) = VQE(Process);
void *(*channel)(void *, int, size_t, const float **, size_t *) = VQE(GetOutputChannelData);
#undef VQE
void *config = NULL, *vqe = NULL;
check(preset("yandexmini_2", &config));
if (input_rate(config) != 48000 || output_rate(config) != 16000 ||
input_chunk(config) != INPUT || mic_count(config) != MICS || speaker_count(config) != SPEAKERS) {
fprintf(stderr, "VQE: incompatible Mini 2 preset\n");
return 1;
}
check(create(config, "/system/vendor/quasar/vqe_models", 0, NULL, &vqe));
free_config(config);
int16_t input[INPUT][MICS + SPEAKERS], output[OUTPUT];
float mics[INPUT][MICS], speakers[INPUT][SPEAKERS];
// Suppress the PDM startup transient while warming the processor (512 ms).
unsigned warmup = 16;
while (fread(input, sizeof(input), 1, stdin) == 1) {
for (size_t i = 0; i < INPUT; ++i) {
for (size_t j = 0; j < MICS; ++j) mics[i][j] = input[i][j] / 32768.0f;
for (size_t j = 0; j < SPEAKERS; ++j) speakers[i][j] = input[i][MICS+j] / 32768.0f;
}
check(process(vqe, &mics[0][0], INPUT * MICS, &speakers[0][0], INPUT * SPEAKERS));
const float *samples = NULL;
size_t count = 0;
check(channel(vqe, ASR_CHANNEL, 0, &samples, &count));
if (count != OUTPUT || !samples) {
fprintf(stderr, "VQE: incompatible output frame\n");
return 1;
}
if (warmup) {
--warmup;
continue;
}
for (size_t i = 0; i < OUTPUT; ++i) output[i] = pcm(samples[i]);
if (fwrite(output, sizeof(output), 1, stdout) != 1 || fflush(stdout) != 0) return 1;
}
destroy(vqe);
return ferror(stdin) ? 1 : 0;
}