Recognizer deleteion safety + data control, remove repeating UTF8ToString() call from event system.

This commit is contained in:
msqr1
2024-09-02 23:02:06 -07:00
parent 070fc73126
commit b0f8464eeb
15 changed files with 136 additions and 102 deletions

View File

@@ -21,7 +21,8 @@ EMSCRIPTEN_BINDINGS() {
.constructor<int, float, CommonModel*>(allow_raw_pointers())
.constructor<int, float, CommonModel*, CommonModel*>(allow_raw_pointers())
.constructor<int, float, CommonModel*, std::string, int>(allow_raw_pointers())
.function("pushData", &Recognizer::pushData, allow_raw_pointers())
.function("safeDelete", &Recognizer::safeDelete, allow_raw_pointers())
.function("acceptWaveform", &Recognizer::acceptWaveform, allow_raw_pointers())
.function("reset", &Recognizer::reset, allow_raw_pointers())
.function("setEndpointerMode", &Recognizer::setEndpointerMode, allow_raw_pointers())
.function("setEndpointerDelays", &Recognizer::setEndpointerDelays, allow_raw_pointers())

View File

@@ -14,25 +14,25 @@ void CommonModel::extractAndLoad(unsigned char* tar, int tarSize) {
free(tar);
switch(res) {
case IncorrectFormat:
fireEv(index, "Untar: Incorrect tar format, must be USTAR");
fireEv(index, Event::status, "Untar: Incorrect tar format, must be USTAR");
return;
case IncorrectFiletype:
fireEv(index, "Untar: Not a directory or regular file");
fireEv(index, Event::status, "Untar: Not a directory or regular file");
return;
case FailedOpen:
fireEv(index, "Untar: Unable to open file for write");
fireEv(index, Event::status, "Untar: Unable to open file for write");
return;
case FailedWrite:
fireEv(index, "Untar: Unable to write file");
fireEv(index, Event::status, "Untar: Unable to write file");
return;
case FailedClose:
fireEv(index, "Untar: Unable to close file after write");
fireEv(index, Event::status, "Untar: Unable to close file after write");
return;
};
if(normalMdl) mdl = vosk_model_new(storepath.c_str());
else mdl = vosk_spk_model_new(storepath.c_str());
if(normalMdl ? std::get<VoskModel*>(mdl) != nullptr : std::get<VoskSpkModel*>(mdl) != nullptr) fireEv(index, "0");
else fireEv(index, "Unable to load model for recognition");
if(normalMdl ? std::get<VoskModel*>(mdl) != nullptr : std::get<VoskSpkModel*>(mdl) != nullptr) fireEv(index, status);
else fireEv(index, status, "Unable to load model for recognition");
fs::remove_all(storepath);
}
int CommonModel::findWord(std::string word) {

View File

@@ -9,6 +9,7 @@ struct CommonModel {
std::string storepath;
std::string id;
std::variant<VoskModel*, VoskSpkModel*> mdl;
void extractAndLoad(unsigned char* tarStart, int tarSize);
int findWord(std::string word);
CommonModel(int index, bool normalMdl, std::string storepath, std::string id, int tarStart, int tarSize);

View File

@@ -1,57 +1,73 @@
#include "Recognizer.h"
#include <atomic>
Recognizer::Recognizer(int index, float sampleRate, CommonModel* model) :
index{index},
rec{vosk_recognizer_new(std::get<VoskModel*>(model->mdl), sampleRate)}
{
finishConstruction(model);
rec{vosk_recognizer_new(std::get<VoskModel*>(model->mdl), sampleRate)} {
if(rec == nullptr) fireEv(index, Event::status, "Unable to initialize recognizer");
else globalPool.exec([this, index]{main(index);});
}
Recognizer::Recognizer(int index, float sampleRate, CommonModel* model, CommonModel* spkModel) :
index{index},
rec{vosk_recognizer_new_spk(std::get<VoskModel*>(model->mdl), sampleRate, std::get<VoskSpkModel*>(spkModel->mdl))} {
finishConstruction(model, spkModel);
if(rec == nullptr) fireEv(index, Event::status, "Unable to initialize recognizer");
else globalPool.exec([this, index]{main(index);});
}
Recognizer::Recognizer(int index, float sampleRate, CommonModel* model, const std::string& grm, int) :
index{index},
rec{vosk_recognizer_new_grm(std::get<VoskModel*>(model->mdl), sampleRate, grm.c_str())} {
finishConstruction(model);
if(rec == nullptr) fireEv(index, Event::status, "Unable to initialize recognizer");
else globalPool.exec([this, index]{main(index);});
}
Recognizer::~Recognizer() {
done = true;
void Recognizer::safeDelete(bool _processCurrent) {
emscripten_atomic_store_u8(&processCurrent, _processCurrent);
emscripten_atomic_store_u8(&done, true);
emscripten_atomic_store_u32(&haveData, true);
emscripten_atomic_notify(&haveData, 1);
while(!dataQ.empty()) {
free(dataQ.front().data);
dataQ.pop();
}
vosk_recognizer_free(rec);
}
void Recognizer::finishConstruction(CommonModel* model, CommonModel* spkModel) {
if(rec == nullptr) fireEv(index, "Unable to initialize recognizer");
else globalPool.exec([this]{main();});
}
void Recognizer::main() {
fireEv(index, "0");
while(!done) {
void Recognizer::main(int index) {
fireEv(index, Event::status);
AudioData* next;
while(!emscripten_atomic_load_u8(&done)) {
if(dataQ.empty()) {
emscripten_atomic_store_u32(&haveData, false);
emscripten_atomic_wait_u32(&haveData, false, -1);
}
else {
AudioData& next = dataQ.front();
switch(vosk_recognizer_accept_waveform_f(rec, next.data, next.len)) {
next = &dataQ.front();
switch(vosk_recognizer_accept_waveform_f(rec, next->data, next->len)) {
case 0: [[likely]]
fireEv(index, vosk_recognizer_partial_result(rec), "partialResult");
fireEv(index, Event::partialResult, vosk_recognizer_partial_result(rec));
break;
case 1: [[unlikely]]
fireEv(index, vosk_recognizer_result(rec), "result");
fireEv(index, Event::result, vosk_recognizer_result(rec));
}
free(next.data);
free(next->data);
dataQ.pop();
}
}
if(emscripten_atomic_load_u8(&processCurrent)) {
while(!dataQ.empty()) {
free(dataQ.front().data);
dataQ.pop();
}
}
else {
while(!dataQ.empty()) {
next = &dataQ.front();
switch(vosk_recognizer_accept_waveform_f(rec, next->data, next->len)) {
case 0: [[likely]]
fireEv(index, Event::partialResult, vosk_recognizer_partial_result(rec));
break;
case 1: [[unlikely]]
fireEv(index, Event::result, vosk_recognizer_result(rec));
}
free(next->data);
dataQ.pop();
}
}
fireEv(index, Event::result, vosk_recognizer_final_result(rec));
vosk_recognizer_free(rec);
fireEv(index, Event::status);
}
void Recognizer::pushData(int start, int len) {
void Recognizer::acceptWaveform(int start, int len) {
dataQ.emplace(start, len);
emscripten_atomic_store_u32(&haveData, true);
emscripten_atomic_notify(&haveData, 1);

View File

@@ -4,18 +4,17 @@
// Prevent naming conflicts with Vosk's Recognizer class
#define Recognizer Recognizer_
struct Recognizer {
int haveData{};
int haveData{};
bool processCurrent{};
bool done{};
int index;
VoskRecognizer* rec;
std::queue<AudioData> dataQ;
Recognizer(int index, float sampleRate, CommonModel* model);
Recognizer(int index, float sampleRate, CommonModel* model, CommonModel* spkModel);
Recognizer(int index, float sampleRate, CommonModel* model, const std::string& grm, int);
~Recognizer();
void finishConstruction(CommonModel* model, CommonModel* spkModel = nullptr);
void main();
void pushData(int start, int len);
void main(int index);
void safeDelete(bool _processCurrent);
void acceptWaveform(int start, int len);
void reset();
void setEndpointerMode(VoskEndpointerMode mode);
void setEndpointerDelays(float tStartMax, float tEnd, float tMax);

View File

@@ -2,19 +2,13 @@
#include <emscripten/em_js.h>
#include <emscripten/wasm_worker.h>
EM_JS(void, _fireEv, (int index, int content, int type), {
objs[index].dispatchEvent(new CustomEvent(type === 0 ? "0" : UTF8ToString(type), { "detail" : UTF8ToString(content) }));
EM_JS(void, _fireEv, (int index, int typeIdx, int content), {
objs[index].dispatchEvent(new CustomEvent(events[typeIdx], { "detail" : content == 0 ? null : UTF8ToString(content) }));
})
void fireEv(int index, const char* _content, const char* _type) {
int content = reinterpret_cast<int>(_content);
int type = reinterpret_cast<int>(_type);
switch(emscripten_wasm_worker_self_id()) {
case 0: [[unlikely]]
_fireEv(index, content, type);
break;
case 1: [[likely]]
emscripten_wasm_worker_post_function_viii(0, _fireEv, index, content, type);
}
void fireEv(int index, int typeIdx, const char* content) {
int contentAddr{reinterpret_cast<int>(content)};
if(emscripten_wasm_worker_self_id()) emscripten_wasm_worker_post_function_viii(0, _fireEv, index, typeIdx, contentAddr);
else _fireEv(index, typeIdx, contentAddr);
}
int untar(unsigned char* tar, int tarSize, const std::string& storepath) {
if(std::memcmp(tar + 257, "ustar", 5)) return IncorrectFormat;

View File

@@ -13,6 +13,14 @@ struct AudioData {
int len;
AudioData(int start, int len) : data{reinterpret_cast<float*>(start)}, len{len} {}
};
enum Event {
// Shared
status,
// Recognizer
partialResult,
result,
};
enum UntarStatus {
Successful,
IncorrectFormat,
@@ -39,7 +47,7 @@ struct WorkerPool {
~WorkerPool();
void exec(std::function<void()> fn);
};
void fireEv(int index, const char* _content, const char* _type = nullptr);
void fireEv(int index, int typeIdx, const char* content = nullptr);
int untar(unsigned char* tar, int tarSize, const std::string& storepath);
extern WorkerPool globalPool;

View File

@@ -1,4 +1,5 @@
let objs = []
let events = ["status", "partialResult", "result"]
let processorURL = URL.createObjectURL(new Blob(['(', (() => {
registerProcessor("VoskletTransferer", class extends AudioWorkletProcessor {
constructor(opts) {
@@ -21,8 +22,8 @@ let processorURL = URL.createObjectURL(new Blob(['(', (() => {
})
}).toString(), ')()'], { type : "text/javascript" }))
Module.cleanUp = () => {
objs.forEach(obj => obj.obj.delete())
Module.cleanUp = async () => {
for(let obj of objs) await obj.delete()
URL.revokeObjectURL(processorURL)
}
@@ -49,15 +50,18 @@ class CommonModel extends EventTarget {
super()
objs.push(this)
}
delete() {
this.obj.delete()
}
static async create(url, storepath, id, normalMdl) {
let mdl = new CommonModel()
let result = new Promise((resolve, reject) => {
mdl.addEventListener("0", ev => {
if(ev.detail == "0") {
mdl.addEventListener("status", ev => {
if(!ev.detail) {
if(normalMdl) mdl.findWord = (word) => mdl.obj.findWord(word)
return resolve(mdl)
resolve(mdl)
}
reject(ev.detail)
else reject(ev.detail)
}, { once : true })
})
let tar
@@ -101,21 +105,38 @@ Module.createSpkModel = async (url, storepath, id) => {
}
class Recognizer extends EventTarget {
constructor() {
constructor() {
super()
objs.push(this)
return new Proxy(this, {
get(self, prop, _) {
return self.obj && Object.keys(Object.getPrototypeOf(self.obj)).includes(prop) ? self.obj[prop].bind(self.obj) : self[prop] ? self[prop].bind ? self[prop].bind(self) : self[prop] : undefined
if(self[prop] == undefined && self.obj[prop] == undefined) return undefined
let p = self[prop]
if(p) return p.bind ? p.bind(self) : p
p = self.obj[prop]
return p.bind ? p.bind(self.obj) : p
}
})
}
acceptWaveform(audioData) {
let start = _malloc(audioData.length * 4)
HEAPF32.set(audioData, start / 4)
this.obj.acceptWaveform(start, audioData.length)
}
async delete(processCurrent = false) {
let result = new Promise((resolve, _) => this.addEventListener("status", _ => {
this.obj.delete()
resolve()
}, { once : true }))
this.obj.safeDelete(processCurrent)
return result;
}
static async create(model, sampleRate, mode, grammar, spkModel) {
let rec = new Recognizer()
let result = new Promise((resolve, reject) => {
rec.addEventListener("0", ev => {
if(ev.detail == "0") return resolve(rec)
reject(ev.detail)
rec.addEventListener("status", ev => {
if(!ev.detail) resolve(rec)
else reject(ev.detail)
}, { once : true })
})
switch(mode) {
@@ -130,11 +151,6 @@ class Recognizer extends EventTarget {
}
return result
}
acceptWaveform(audioData) {
let start = _malloc(audioData.length * 4)
HEAPF32.set(audioData, start / 4)
this.obj.pushData(start, audioData.length)
}
}
Module.createRecognizer = (model, sampleRate) => {

View File

@@ -1,5 +1,5 @@
#!/bin/bash
INITIAL_MEMORY=${INITIAL_MEMORY:-300mb}
INITIAL_MEMORY=${INITIAL_MEMORY:-315mb}
MAX_THREADS=${MAX_THREADS:-1}
EMSDK=${EMSDK:-../emsdk}
JOBS=${JOBS:-$(nproc)}