99 lines
3.7 KiB
C
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;
|
|
}
|