react-native-nitro-onnx 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -31
- package/android/CMakeLists.txt +5 -3
- package/android/src/main/AndroidManifest.xml +0 -2
- package/android/src/main/cpp/cpp-adapter.cpp +2 -2
- package/android/src/main/java/com/margelo/nitro/onnx/speech/OnnxSpeechPackage.kt +24 -15
- package/cpp/AsrEngine.cpp +150 -58
- package/cpp/AsrEngine.hpp +12 -7
- package/cpp/AudioFileReader.cpp +4 -0
- package/cpp/ModelSingleton.hpp +14 -0
- package/cpp/NitroOnnxSpeech.cpp +8 -9
- package/cpp/NitroOnnxSpeech.hpp +0 -6
- package/cpp/OfflineAsr.cpp +9 -12
- package/cpp/OfflineAsr.hpp +1 -1
- package/cpp/ResourceDir.cpp +5 -5
- package/cpp/ResourceDir.hpp +5 -4
- package/cpp/SpeakerEngine.cpp +38 -28
- package/cpp/SpeakerEngine.hpp +14 -13
- package/cpp/SpeakerManager.cpp +16 -14
- package/cpp/SpeakerManager.hpp +2 -1
- package/cpp/SpeakerRecord.cpp +125 -0
- package/cpp/SpeakerRecord.hpp +42 -0
- package/cpp/StreamingAsr.cpp +13 -10
- package/cpp/StreamingAsr.hpp +1 -1
- package/cpp/Tts.cpp +38 -10
- package/cpp/Tts.hpp +2 -1
- package/cpp/TtsEngine.cpp +15 -7
- package/cpp/TtsEngine.hpp +18 -5
- package/cpp/Vad.cpp +11 -11
- package/cpp/Vad.hpp +1 -1
- package/cpp/VadEngine.cpp +26 -33
- package/cpp/VadEngine.hpp +8 -10
- package/cpp/Version.hpp +7 -0
- package/ios/OnnxSpeechInitializer.mm +18 -6
- package/lib/specs/OnnxSpeech.nitro.d.ts +43 -6
- package/lib/specs/OnnxSpeech.nitro.d.ts.map +1 -1
- package/package.json +4 -3
- package/scripts/generate-version.js +22 -0
- package/src/specs/OnnxSpeech.nitro.ts +25 -8
- package/cpp/ThreadPool.cpp +0 -41
- package/cpp/ThreadPool.hpp +0 -62
package/cpp/AsrEngine.cpp
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
#include "AudioFileReader.hpp"
|
|
7
7
|
#include "sherpa-onnx/c-api/c-api.h"
|
|
8
8
|
|
|
9
|
+
#include <cmath>
|
|
9
10
|
#include <cstring>
|
|
10
|
-
#include <fstream>
|
|
11
11
|
#include <stdexcept>
|
|
12
12
|
|
|
13
13
|
namespace margelo::nitro::onnx::speech {
|
|
@@ -33,25 +33,74 @@ std::string joinPath(const std::string& dir, const std::string& file) {
|
|
|
33
33
|
ModelSingleton<const SherpaOnnxOfflineRecognizer> gOfflineRecognizerCache;
|
|
34
34
|
ModelSingleton<const SherpaOnnxOnlineRecognizer> gOnlineRecognizerCache;
|
|
35
35
|
|
|
36
|
+
float meanTokenConfidence(const float* logProbs, int32_t count) {
|
|
37
|
+
if (logProbs == nullptr || count <= 0) {
|
|
38
|
+
return 0.0f;
|
|
39
|
+
}
|
|
40
|
+
double sum = 0.0;
|
|
41
|
+
for (int32_t i = 0; i < count; ++i) {
|
|
42
|
+
sum += logProbs[i];
|
|
43
|
+
}
|
|
44
|
+
return static_cast<float>(std::exp(sum / count));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void fillTimestampsMs(AsrEngineResult& result, const float* timestamps, int32_t count) {
|
|
48
|
+
if (timestamps == nullptr || count <= 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
result.timestamps.assign(timestamps, timestamps + count);
|
|
52
|
+
for (float& t : result.timestamps) {
|
|
53
|
+
t *= 1000.0f;
|
|
54
|
+
}
|
|
55
|
+
if (!result.timestamps.empty()) {
|
|
56
|
+
result.startMs = result.timestamps.front();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
AsrEngineResult fromOfflineResult(const SherpaOnnxOfflineRecognizerResult& r, float endMs) {
|
|
61
|
+
AsrEngineResult result;
|
|
62
|
+
result.text = r.text ? r.text : "";
|
|
63
|
+
result.endMs = endMs;
|
|
64
|
+
result.score = meanTokenConfidence(r.ys_log_probs, r.count);
|
|
65
|
+
fillTimestampsMs(result, r.timestamps, r.count);
|
|
66
|
+
if (r.json) {
|
|
67
|
+
result.json = r.json;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
AsrEngineResult fromOnlineResult(const SherpaOnnxOnlineRecognizerResult& r) {
|
|
73
|
+
AsrEngineResult result;
|
|
74
|
+
result.text = r.text ? r.text : "";
|
|
75
|
+
fillTimestampsMs(result, r.timestamps, r.count);
|
|
76
|
+
if (r.json) {
|
|
77
|
+
result.json = r.json;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
36
82
|
} // namespace
|
|
37
83
|
|
|
84
|
+
std::string AsrEngineConfig::cacheSignature() const {
|
|
85
|
+
return modelDir + "|" + std::to_string(static_cast<int>(type)) + "|" + provider + "|" +
|
|
86
|
+
std::to_string(numThreads) + "|" + language + "|" + decodingMethod + "|" +
|
|
87
|
+
std::to_string(maxActivePaths) + "|" + (useItn ? "1" : "0");
|
|
88
|
+
}
|
|
89
|
+
|
|
38
90
|
// ------------------------------------------------------------------------------
|
|
39
91
|
// Offline ASR
|
|
40
92
|
// ------------------------------------------------------------------------------
|
|
41
93
|
|
|
42
|
-
OfflineAsrEngine::OfflineAsrEngine(std::shared_ptr<ThreadPool> threadPool)
|
|
43
|
-
: threadPool_(std::move(threadPool)) {}
|
|
44
|
-
|
|
45
94
|
OfflineAsrEngine::~OfflineAsrEngine() {
|
|
46
95
|
unload();
|
|
47
96
|
}
|
|
48
97
|
|
|
49
98
|
void OfflineAsrEngine::load(const AsrEngineConfig& config) {
|
|
50
99
|
unload();
|
|
100
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
51
101
|
config_ = config;
|
|
52
102
|
|
|
53
|
-
|
|
54
|
-
auto cached = gOfflineRecognizerCache.getOrCreate(key, [this](const std::string&) {
|
|
103
|
+
auto cached = gOfflineRecognizerCache.getOrCreate(config_.cacheSignature(), [this](const std::string&) {
|
|
55
104
|
SherpaOnnxOfflineRecognizerConfig c;
|
|
56
105
|
std::memset(&c, 0, sizeof(c));
|
|
57
106
|
|
|
@@ -70,6 +119,7 @@ void OfflineAsrEngine::load(const AsrEngineConfig& config) {
|
|
|
70
119
|
c.model_config.whisper.decoder = whisperDecoder.c_str();
|
|
71
120
|
c.model_config.whisper.language = config_.language.c_str();
|
|
72
121
|
c.model_config.whisper.tail_paddings = 2;
|
|
122
|
+
c.model_config.model_type = "whisper";
|
|
73
123
|
break;
|
|
74
124
|
case AsrModelType::TRANSDUCER:
|
|
75
125
|
case AsrModelType::ZIPFORMER:
|
|
@@ -79,18 +129,44 @@ void OfflineAsrEngine::load(const AsrEngineConfig& config) {
|
|
|
79
129
|
c.model_config.transducer.joiner = joiner.c_str();
|
|
80
130
|
break;
|
|
81
131
|
case AsrModelType::PARAFORMER:
|
|
132
|
+
c.model_config.paraformer.model = model.c_str();
|
|
133
|
+
c.model_config.model_type = "paraformer";
|
|
134
|
+
break;
|
|
82
135
|
case AsrModelType::WENET:
|
|
136
|
+
c.model_config.wenet_ctc.model = model.c_str();
|
|
137
|
+
c.model_config.model_type = "wenet_ctc";
|
|
138
|
+
break;
|
|
83
139
|
case AsrModelType::TELESPEECH:
|
|
140
|
+
c.model_config.telespeech_ctc = model.c_str();
|
|
141
|
+
c.model_config.model_type = "telespeech_ctc";
|
|
142
|
+
break;
|
|
84
143
|
case AsrModelType::SENSE_VOICE:
|
|
85
|
-
c.model_config.
|
|
144
|
+
c.model_config.sense_voice.model = model.c_str();
|
|
145
|
+
c.model_config.sense_voice.language = config_.language.c_str();
|
|
146
|
+
c.model_config.sense_voice.use_itn = config_.useItn ? 1 : 0;
|
|
147
|
+
c.model_config.model_type = "sense_voice";
|
|
86
148
|
break;
|
|
87
149
|
case AsrModelType::MOONSHINE:
|
|
150
|
+
// Moonshine layout: model=preprocessor, encoder=encoder,
|
|
151
|
+
// decoder=uncached_decoder (or merged_decoder when joiner is empty),
|
|
152
|
+
// joiner=cached_decoder.
|
|
153
|
+
c.model_config.moonshine.preprocessor = model.c_str();
|
|
154
|
+
c.model_config.moonshine.encoder = encoder.c_str();
|
|
155
|
+
if (joiner.empty()) {
|
|
156
|
+
c.model_config.moonshine.merged_decoder = decoder.c_str();
|
|
157
|
+
} else {
|
|
158
|
+
c.model_config.moonshine.uncached_decoder = decoder.c_str();
|
|
159
|
+
c.model_config.moonshine.cached_decoder = joiner.c_str();
|
|
160
|
+
}
|
|
161
|
+
c.model_config.model_type = "moonshine";
|
|
162
|
+
break;
|
|
88
163
|
case AsrModelType::DOLPHIN:
|
|
164
|
+
c.model_config.dolphin.model = model.c_str();
|
|
165
|
+
c.model_config.model_type = "dolphin";
|
|
166
|
+
break;
|
|
89
167
|
case AsrModelType::NEMO:
|
|
90
168
|
c.model_config.nemo_ctc.model = model.c_str();
|
|
91
|
-
|
|
92
|
-
c.model_config.model_type = "nemo";
|
|
93
|
-
}
|
|
169
|
+
c.model_config.model_type = "nemo";
|
|
94
170
|
break;
|
|
95
171
|
}
|
|
96
172
|
|
|
@@ -113,34 +189,34 @@ void OfflineAsrEngine::load(const AsrEngineConfig& config) {
|
|
|
113
189
|
}
|
|
114
190
|
|
|
115
191
|
bool OfflineAsrEngine::isLoaded() const {
|
|
192
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
116
193
|
return recognizer_ != nullptr;
|
|
117
194
|
}
|
|
118
195
|
|
|
119
196
|
AsrEngineResult OfflineAsrEngine::recognize(const std::vector<float>& samples) {
|
|
120
|
-
|
|
197
|
+
// Copy the shared_ptr under the lock, then decode without holding it so
|
|
198
|
+
// concurrent recognize() calls can share the same const recognizer.
|
|
199
|
+
std::shared_ptr<const SherpaOnnxOfflineRecognizer> recognizer;
|
|
200
|
+
{
|
|
201
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
202
|
+
recognizer = recognizer_;
|
|
203
|
+
}
|
|
204
|
+
if (recognizer == nullptr) {
|
|
121
205
|
throw std::runtime_error("Offline ASR not loaded");
|
|
122
206
|
}
|
|
123
207
|
|
|
124
|
-
const SherpaOnnxOfflineStream* stream = SherpaOnnxCreateOfflineStream(
|
|
208
|
+
const SherpaOnnxOfflineStream* stream = SherpaOnnxCreateOfflineStream(recognizer.get());
|
|
125
209
|
SherpaOnnxAcceptWaveformOffline(stream, 16000, samples.data(), static_cast<int32_t>(samples.size()));
|
|
126
|
-
SherpaOnnxDecodeOfflineStream(
|
|
210
|
+
SherpaOnnxDecodeOfflineStream(recognizer.get(), stream);
|
|
127
211
|
|
|
128
|
-
const
|
|
212
|
+
const SherpaOnnxOfflineRecognizerResult* raw = SherpaOnnxGetOfflineStreamResult(stream);
|
|
129
213
|
AsrEngineResult result;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const char* textStart = std::strstr(result.json.c_str(), textKey);
|
|
136
|
-
if (textStart != nullptr) {
|
|
137
|
-
textStart += std::strlen(textKey);
|
|
138
|
-
const char* textEnd = std::strstr(textStart, "\"");
|
|
139
|
-
if (textEnd != nullptr) {
|
|
140
|
-
result.text = std::string(textStart, textEnd);
|
|
141
|
-
}
|
|
214
|
+
if (raw != nullptr) {
|
|
215
|
+
result = fromOfflineResult(*raw, samplesToMs(static_cast<int32_t>(samples.size())));
|
|
216
|
+
SherpaOnnxDestroyOfflineRecognizerResult(raw);
|
|
217
|
+
} else {
|
|
218
|
+
result.endMs = samplesToMs(static_cast<int32_t>(samples.size()));
|
|
142
219
|
}
|
|
143
|
-
result.endMs = samplesToMs(static_cast<int32_t>(samples.size()));
|
|
144
220
|
|
|
145
221
|
SherpaOnnxDestroyOfflineStream(stream);
|
|
146
222
|
return result;
|
|
@@ -157,6 +233,7 @@ AsrEngineResult OfflineAsrEngine::recognizeFile(const std::string& path) {
|
|
|
157
233
|
}
|
|
158
234
|
|
|
159
235
|
void OfflineAsrEngine::unload() {
|
|
236
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
160
237
|
recognizer_.reset();
|
|
161
238
|
}
|
|
162
239
|
|
|
@@ -164,9 +241,6 @@ void OfflineAsrEngine::unload() {
|
|
|
164
241
|
// Streaming ASR
|
|
165
242
|
// ------------------------------------------------------------------------------
|
|
166
243
|
|
|
167
|
-
StreamingAsrEngine::StreamingAsrEngine(std::shared_ptr<ThreadPool> threadPool)
|
|
168
|
-
: threadPool_(std::move(threadPool)) {}
|
|
169
|
-
|
|
170
244
|
StreamingAsrEngine::~StreamingAsrEngine() {
|
|
171
245
|
unload();
|
|
172
246
|
}
|
|
@@ -175,10 +249,11 @@ void StreamingAsrEngine::load(
|
|
|
175
249
|
const AsrEngineConfig& config,
|
|
176
250
|
std::shared_ptr<StreamingAsrListener> listener) {
|
|
177
251
|
unload();
|
|
252
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
178
253
|
config_ = config;
|
|
179
254
|
listener_ = std::move(listener);
|
|
180
255
|
|
|
181
|
-
const std::string key = config_.
|
|
256
|
+
const std::string key = config_.cacheSignature() + "|streaming";
|
|
182
257
|
auto cached = gOnlineRecognizerCache.getOrCreate(key, [this](const std::string&) {
|
|
183
258
|
SherpaOnnxOnlineRecognizerConfig c;
|
|
184
259
|
std::memset(&c, 0, sizeof(c));
|
|
@@ -197,7 +272,7 @@ void StreamingAsrEngine::load(
|
|
|
197
272
|
c.model_config.transducer.joiner = joiner.c_str();
|
|
198
273
|
break;
|
|
199
274
|
default:
|
|
200
|
-
throw std::runtime_error("Streaming ASR
|
|
275
|
+
throw std::runtime_error("Streaming ASR only supports transducer / zipformer / conformer models");
|
|
201
276
|
}
|
|
202
277
|
|
|
203
278
|
c.model_config.tokens = tokens.c_str();
|
|
@@ -222,48 +297,64 @@ void StreamingAsrEngine::load(
|
|
|
222
297
|
}
|
|
223
298
|
|
|
224
299
|
bool StreamingAsrEngine::isLoaded() const {
|
|
300
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
225
301
|
return recognizer_ != nullptr && stream_ != nullptr;
|
|
226
302
|
}
|
|
227
303
|
|
|
228
304
|
void StreamingAsrEngine::acceptWaveform(const std::vector<float>& samples) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
305
|
+
std::shared_ptr<StreamingAsrListener> listener;
|
|
306
|
+
AsrEngineResult result;
|
|
307
|
+
bool hasResult = false;
|
|
308
|
+
{
|
|
309
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
310
|
+
if (recognizer_ == nullptr || stream_ == nullptr) {
|
|
311
|
+
throw std::runtime_error("Streaming ASR not loaded");
|
|
312
|
+
}
|
|
313
|
+
SherpaOnnxOnlineStreamAcceptWaveform(stream_.get(), 16000, samples.data(), static_cast<int32_t>(samples.size()));
|
|
314
|
+
|
|
315
|
+
if (SherpaOnnxIsOnlineStreamReady(recognizer_.get(), stream_.get())) {
|
|
316
|
+
SherpaOnnxDecodeOnlineStream(recognizer_.get(), stream_.get());
|
|
317
|
+
const SherpaOnnxOnlineRecognizerResult* raw =
|
|
318
|
+
SherpaOnnxGetOnlineStreamResult(recognizer_.get(), stream_.get());
|
|
319
|
+
if (raw != nullptr) {
|
|
320
|
+
result = fromOnlineResult(*raw);
|
|
321
|
+
SherpaOnnxDestroyOnlineRecognizerResult(raw);
|
|
322
|
+
hasResult = true;
|
|
323
|
+
}
|
|
243
324
|
}
|
|
325
|
+
listener = listener_.lock();
|
|
326
|
+
}
|
|
327
|
+
if (hasResult && listener) {
|
|
328
|
+
listener->onPartialResult(result);
|
|
244
329
|
}
|
|
245
330
|
}
|
|
246
331
|
|
|
247
332
|
AsrEngineResult StreamingAsrEngine::finalize() {
|
|
248
|
-
|
|
249
|
-
throw std::runtime_error("Streaming ASR not loaded");
|
|
250
|
-
}
|
|
251
|
-
SherpaOnnxOnlineStreamInputFinished(stream_.get());
|
|
252
|
-
SherpaOnnxDecodeOnlineStream(recognizer_.get(), stream_.get());
|
|
253
|
-
const char* json = SherpaOnnxGetOnlineStreamResultAsJson(recognizer_.get(), stream_.get());
|
|
333
|
+
std::shared_ptr<StreamingAsrListener> listener;
|
|
254
334
|
AsrEngineResult result;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
335
|
+
{
|
|
336
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
337
|
+
if (recognizer_ == nullptr || stream_ == nullptr) {
|
|
338
|
+
throw std::runtime_error("Streaming ASR not loaded");
|
|
339
|
+
}
|
|
340
|
+
SherpaOnnxOnlineStreamInputFinished(stream_.get());
|
|
341
|
+
SherpaOnnxDecodeOnlineStream(recognizer_.get(), stream_.get());
|
|
342
|
+
const SherpaOnnxOnlineRecognizerResult* raw =
|
|
343
|
+
SherpaOnnxGetOnlineStreamResult(recognizer_.get(), stream_.get());
|
|
344
|
+
if (raw != nullptr) {
|
|
345
|
+
result = fromOnlineResult(*raw);
|
|
346
|
+
SherpaOnnxDestroyOnlineRecognizerResult(raw);
|
|
347
|
+
}
|
|
348
|
+
listener = listener_.lock();
|
|
259
349
|
}
|
|
260
|
-
if (
|
|
261
|
-
|
|
350
|
+
if (listener) {
|
|
351
|
+
listener->onFinalResult(result);
|
|
262
352
|
}
|
|
263
353
|
return result;
|
|
264
354
|
}
|
|
265
355
|
|
|
266
356
|
void StreamingAsrEngine::reset() {
|
|
357
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
267
358
|
if (recognizer_ != nullptr) {
|
|
268
359
|
stream_ = std::shared_ptr<const SherpaOnnxOnlineStream>(
|
|
269
360
|
SherpaOnnxCreateOnlineStream(recognizer_.get()),
|
|
@@ -272,6 +363,7 @@ void StreamingAsrEngine::reset() {
|
|
|
272
363
|
}
|
|
273
364
|
|
|
274
365
|
void StreamingAsrEngine::unload() {
|
|
366
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
275
367
|
stream_.reset();
|
|
276
368
|
recognizer_.reset();
|
|
277
369
|
listener_.reset();
|
package/cpp/AsrEngine.hpp
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
#include "AudioUtils.hpp"
|
|
11
11
|
#include "ModelSingleton.hpp"
|
|
12
|
-
#include "ThreadPool.hpp"
|
|
13
12
|
|
|
14
13
|
#include "AsrModelType.hpp"
|
|
15
14
|
|
|
16
15
|
#include <memory>
|
|
16
|
+
#include <mutex>
|
|
17
17
|
#include <string>
|
|
18
18
|
#include <vector>
|
|
19
19
|
|
|
@@ -42,13 +42,18 @@ struct AsrEngineConfig {
|
|
|
42
42
|
std::string language = "en";
|
|
43
43
|
bool useItn = true;
|
|
44
44
|
bool debug = false;
|
|
45
|
-
#
|
|
45
|
+
#if defined(__ANDROID__) && defined(SHERPA_ONNX_ENABLE_QNN)
|
|
46
46
|
std::string provider = "qnn";
|
|
47
|
+
#elif defined(__ANDROID__)
|
|
48
|
+
std::string provider = "nnapi";
|
|
47
49
|
#elif defined(__APPLE__)
|
|
48
50
|
std::string provider = "coreml";
|
|
49
51
|
#else
|
|
50
52
|
std::string provider = "cpu";
|
|
51
53
|
#endif
|
|
54
|
+
|
|
55
|
+
/** Cache-key signature covering options that change recognizer identity. */
|
|
56
|
+
std::string cacheSignature() const;
|
|
52
57
|
};
|
|
53
58
|
|
|
54
59
|
/** Native recognition result before conversion to the generated AsrResult. */
|
|
@@ -73,7 +78,7 @@ class StreamingAsrListener {
|
|
|
73
78
|
/** Offline ASR engine. */
|
|
74
79
|
class OfflineAsrEngine final {
|
|
75
80
|
public:
|
|
76
|
-
|
|
81
|
+
OfflineAsrEngine() = default;
|
|
77
82
|
~OfflineAsrEngine();
|
|
78
83
|
|
|
79
84
|
OfflineAsrEngine(const OfflineAsrEngine&) = delete;
|
|
@@ -86,7 +91,7 @@ class OfflineAsrEngine final {
|
|
|
86
91
|
void unload();
|
|
87
92
|
|
|
88
93
|
private:
|
|
89
|
-
std::
|
|
94
|
+
mutable std::mutex mutex_;
|
|
90
95
|
AsrEngineConfig config_;
|
|
91
96
|
std::shared_ptr<const SherpaOnnxOfflineRecognizer> recognizer_;
|
|
92
97
|
};
|
|
@@ -94,7 +99,7 @@ class OfflineAsrEngine final {
|
|
|
94
99
|
/** Streaming ASR engine. */
|
|
95
100
|
class StreamingAsrEngine final {
|
|
96
101
|
public:
|
|
97
|
-
|
|
102
|
+
StreamingAsrEngine() = default;
|
|
98
103
|
~StreamingAsrEngine();
|
|
99
104
|
|
|
100
105
|
StreamingAsrEngine(const StreamingAsrEngine&) = delete;
|
|
@@ -108,9 +113,9 @@ class StreamingAsrEngine final {
|
|
|
108
113
|
void unload();
|
|
109
114
|
|
|
110
115
|
private:
|
|
111
|
-
std::
|
|
116
|
+
mutable std::mutex mutex_;
|
|
112
117
|
AsrEngineConfig config_;
|
|
113
|
-
std::
|
|
118
|
+
std::weak_ptr<StreamingAsrListener> listener_;
|
|
114
119
|
std::shared_ptr<const SherpaOnnxOnlineRecognizer> recognizer_;
|
|
115
120
|
std::shared_ptr<const SherpaOnnxOnlineStream> stream_;
|
|
116
121
|
};
|
package/cpp/AudioFileReader.cpp
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// ------------------------------------------------------------------------------
|
|
4
4
|
#include "AudioFileReader.hpp"
|
|
5
5
|
|
|
6
|
+
#include <bit>
|
|
6
7
|
#include <cmath>
|
|
7
8
|
#include <cstring>
|
|
8
9
|
#include <fstream>
|
|
@@ -12,6 +13,9 @@ namespace margelo::nitro::onnx::speech {
|
|
|
12
13
|
|
|
13
14
|
namespace {
|
|
14
15
|
|
|
16
|
+
static_assert(std::endian::native == std::endian::little,
|
|
17
|
+
"WAV reader assumes little-endian targets");
|
|
18
|
+
|
|
15
19
|
constexpr int32_t kTargetSampleRate = 16000;
|
|
16
20
|
|
|
17
21
|
struct WavHeader {
|
package/cpp/ModelSingleton.hpp
CHANGED
|
@@ -16,6 +16,9 @@ namespace margelo::nitro::onnx::speech {
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Thread-safe cache for heavy model instances.
|
|
19
|
+
* The cache key must cover every config field that changes model identity
|
|
20
|
+
* (paths, provider, thread count, language, ...), otherwise a later load with
|
|
21
|
+
* different options would incorrectly reuse the first instance.
|
|
19
22
|
* @tparam T The native model type (e.g. SherpaOnnxOfflineRecognizer).
|
|
20
23
|
*/
|
|
21
24
|
template <typename T>
|
|
@@ -26,6 +29,7 @@ class ModelSingleton final {
|
|
|
26
29
|
/** Return a cached instance or create one using factory. */
|
|
27
30
|
std::shared_ptr<T> getOrCreate(const std::string& key, const Factory& factory) {
|
|
28
31
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
32
|
+
pruneExpiredLocked();
|
|
29
33
|
auto it = instances_.find(key);
|
|
30
34
|
if (it != instances_.end()) {
|
|
31
35
|
if (auto alive = it->second.lock()) {
|
|
@@ -44,6 +48,16 @@ class ModelSingleton final {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
private:
|
|
51
|
+
void pruneExpiredLocked() {
|
|
52
|
+
for (auto it = instances_.begin(); it != instances_.end();) {
|
|
53
|
+
if (it->second.expired()) {
|
|
54
|
+
it = instances_.erase(it);
|
|
55
|
+
} else {
|
|
56
|
+
++it;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
std::mutex mutex_;
|
|
48
62
|
std::unordered_map<std::string, std::weak_ptr<T>> instances_;
|
|
49
63
|
};
|
package/cpp/NitroOnnxSpeech.cpp
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#include "StreamingAsr.hpp"
|
|
10
10
|
#include "Tts.hpp"
|
|
11
11
|
#include "Vad.hpp"
|
|
12
|
+
#include "Version.hpp"
|
|
12
13
|
|
|
13
14
|
#include <cstdio>
|
|
14
15
|
#include <cstring>
|
|
@@ -20,34 +21,32 @@
|
|
|
20
21
|
namespace margelo::nitro::onnx::speech {
|
|
21
22
|
|
|
22
23
|
NitroOnnxSpeech::NitroOnnxSpeech()
|
|
23
|
-
: HybridObject(TAG)
|
|
24
|
-
threadPool_(std::make_shared<ThreadPool>()),
|
|
25
|
-
cacheDir_(getCacheDir()) {}
|
|
24
|
+
: HybridObject(TAG) {}
|
|
26
25
|
|
|
27
26
|
NitroOnnxSpeech::~NitroOnnxSpeech() = default;
|
|
28
27
|
|
|
29
28
|
std::shared_ptr<HybridVadSpec> NitroOnnxSpeech::createVad() {
|
|
30
|
-
return std::make_shared<Vad>(
|
|
29
|
+
return std::make_shared<Vad>();
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
std::shared_ptr<HybridOfflineAsrSpec> NitroOnnxSpeech::createOfflineAsr() {
|
|
34
|
-
return std::make_shared<OfflineAsr>(
|
|
33
|
+
return std::make_shared<OfflineAsr>();
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
std::shared_ptr<HybridStreamingAsrSpec> NitroOnnxSpeech::createStreamingAsr() {
|
|
38
|
-
return std::make_shared<StreamingAsr>(
|
|
37
|
+
return std::make_shared<StreamingAsr>();
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
std::shared_ptr<HybridTtsSpec> NitroOnnxSpeech::createTts() {
|
|
42
|
-
return std::make_shared<Tts>(
|
|
41
|
+
return std::make_shared<Tts>();
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
std::shared_ptr<HybridSpeakerManagerSpec> NitroOnnxSpeech::createSpeakerManager() {
|
|
46
|
-
return std::make_shared<SpeakerManager>(
|
|
45
|
+
return std::make_shared<SpeakerManager>();
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
std::string NitroOnnxSpeech::getVersion() {
|
|
50
|
-
return
|
|
49
|
+
return NITRO_ONNX_SPEECH_VERSION;
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
std::string NitroOnnxSpeech::getQualcommSoc() {
|
package/cpp/NitroOnnxSpeech.hpp
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
// ------------------------------------------------------------------------------
|
|
5
5
|
#pragma once
|
|
6
6
|
|
|
7
|
-
#include "ThreadPool.hpp"
|
|
8
|
-
|
|
9
7
|
#include <NitroModules/HybridObject.hpp>
|
|
10
8
|
#include <HybridOnnxSpeechSpec.hpp>
|
|
11
9
|
#include <HybridOfflineAsrSpec.hpp>
|
|
@@ -33,10 +31,6 @@ class NitroOnnxSpeech : public HybridOnnxSpeechSpec {
|
|
|
33
31
|
std::shared_ptr<HybridSpeakerManagerSpec> createSpeakerManager() override;
|
|
34
32
|
std::string getVersion() override;
|
|
35
33
|
std::string getQualcommSoc() override;
|
|
36
|
-
|
|
37
|
-
private:
|
|
38
|
-
std::shared_ptr<ThreadPool> threadPool_;
|
|
39
|
-
std::string cacheDir_;
|
|
40
34
|
};
|
|
41
35
|
|
|
42
36
|
} // namespace margelo::nitro::onnx::speech
|
package/cpp/OfflineAsr.cpp
CHANGED
|
@@ -28,15 +28,14 @@ AsrResult toAsrResult(const AsrEngineResult& native) {
|
|
|
28
28
|
|
|
29
29
|
} // namespace
|
|
30
30
|
|
|
31
|
-
OfflineAsr::OfflineAsr(
|
|
32
|
-
: HybridObject(TAG), engine_(std::move(threadPool)) {}
|
|
31
|
+
OfflineAsr::OfflineAsr() : HybridObject(TAG) {}
|
|
33
32
|
|
|
34
33
|
OfflineAsr::~OfflineAsr() {
|
|
35
34
|
engine_.unload();
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
std::shared_ptr<Promise<void>> OfflineAsr::load(const AsrModelConfig& config) {
|
|
39
|
-
return Promise<void>::async([
|
|
38
|
+
return Promise<void>::async([self = shared_cast<OfflineAsr>(), config]() {
|
|
40
39
|
AsrEngineConfig native;
|
|
41
40
|
native.type = config.type;
|
|
42
41
|
native.modelDir = config.modelDir;
|
|
@@ -54,18 +53,16 @@ std::shared_ptr<Promise<void>> OfflineAsr::load(const AsrModelConfig& config) {
|
|
|
54
53
|
native.language = config.language.value_or("en");
|
|
55
54
|
native.useItn = config.useItn.value_or(true);
|
|
56
55
|
native.debug = config.debug.value_or(false);
|
|
57
|
-
#
|
|
58
|
-
#if defined(SHERPA_ONNX_ENABLE_QNN)
|
|
56
|
+
#if defined(__ANDROID__) && defined(SHERPA_ONNX_ENABLE_QNN)
|
|
59
57
|
native.provider = config.provider.value_or("qnn");
|
|
60
|
-
|
|
58
|
+
#elif defined(__ANDROID__)
|
|
61
59
|
native.provider = config.provider.value_or("nnapi");
|
|
62
|
-
#endif
|
|
63
60
|
#elif defined(__APPLE__)
|
|
64
61
|
native.provider = config.provider.value_or("coreml");
|
|
65
62
|
#else
|
|
66
63
|
native.provider = config.provider.value_or("cpu");
|
|
67
64
|
#endif
|
|
68
|
-
engine_.load(native);
|
|
65
|
+
self->engine_.load(native);
|
|
69
66
|
});
|
|
70
67
|
}
|
|
71
68
|
|
|
@@ -75,19 +72,19 @@ bool OfflineAsr::isLoaded() {
|
|
|
75
72
|
|
|
76
73
|
std::shared_ptr<Promise<AsrResult>> OfflineAsr::recognize(
|
|
77
74
|
const std::shared_ptr<ArrayBuffer>& samples) {
|
|
78
|
-
return Promise<AsrResult>::async([
|
|
75
|
+
return Promise<AsrResult>::async([self = shared_cast<OfflineAsr>(), samples]() {
|
|
79
76
|
auto floatSamples = bytesToFloatVector(samples->data(), samples->size());
|
|
80
|
-
return toAsrResult(engine_.recognize(floatSamples));
|
|
77
|
+
return toAsrResult(self->engine_.recognize(floatSamples));
|
|
81
78
|
});
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
std::shared_ptr<Promise<AsrResult>> OfflineAsr::recognizeFile(const std::string& path) {
|
|
85
82
|
return Promise<AsrResult>::async(
|
|
86
|
-
[
|
|
83
|
+
[self = shared_cast<OfflineAsr>(), path]() { return toAsrResult(self->engine_.recognizeFile(path)); });
|
|
87
84
|
}
|
|
88
85
|
|
|
89
86
|
std::shared_ptr<Promise<void>> OfflineAsr::unload() {
|
|
90
|
-
return Promise<void>::async([
|
|
87
|
+
return Promise<void>::async([self = shared_cast<OfflineAsr>()]() { self->engine_.unload(); });
|
|
91
88
|
}
|
|
92
89
|
|
|
93
90
|
} // namespace margelo::nitro::onnx::speech
|
package/cpp/OfflineAsr.hpp
CHANGED
|
@@ -17,7 +17,7 @@ class OfflineAsr : public HybridOfflineAsrSpec {
|
|
|
17
17
|
public:
|
|
18
18
|
static constexpr auto TAG = "OfflineAsr";
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
OfflineAsr();
|
|
21
21
|
~OfflineAsr() override;
|
|
22
22
|
|
|
23
23
|
std::shared_ptr<Promise<void>> load(const AsrModelConfig& config) override;
|
package/cpp/ResourceDir.cpp
CHANGED
|
@@ -7,7 +7,7 @@ namespace margelo::nitro::onnx::speech {
|
|
|
7
7
|
|
|
8
8
|
namespace {
|
|
9
9
|
std::string g_resourceDir;
|
|
10
|
-
std::string
|
|
10
|
+
std::string g_documentDir;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const std::string& getResourceDir() {
|
|
@@ -18,12 +18,12 @@ void setResourceDir(const std::string& dir) {
|
|
|
18
18
|
g_resourceDir = dir;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const std::string&
|
|
22
|
-
return
|
|
21
|
+
const std::string& getDocumentDir() {
|
|
22
|
+
return g_documentDir;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
void
|
|
26
|
-
|
|
25
|
+
void setDocumentDir(const std::string& dir) {
|
|
26
|
+
g_documentDir = dir;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
} // namespace margelo::nitro::onnx::speech
|
package/cpp/ResourceDir.hpp
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// ------------------------------------------------------------------------------
|
|
2
2
|
// ResourceDir.hpp
|
|
3
|
-
// Process-wide directories
|
|
4
|
-
//
|
|
3
|
+
// Process-wide directories set once from the platform layer at startup.
|
|
4
|
+
// resourceDir — bundled read-only assets (e.g. silero_vad.onnx)
|
|
5
|
+
// documentDir — writable app documents (registered speakers)
|
|
5
6
|
// ------------------------------------------------------------------------------
|
|
6
7
|
#pragma once
|
|
7
8
|
|
|
@@ -12,7 +13,7 @@ namespace margelo::nitro::onnx::speech {
|
|
|
12
13
|
const std::string& getResourceDir();
|
|
13
14
|
void setResourceDir(const std::string& dir);
|
|
14
15
|
|
|
15
|
-
const std::string&
|
|
16
|
-
void
|
|
16
|
+
const std::string& getDocumentDir();
|
|
17
|
+
void setDocumentDir(const std::string& dir);
|
|
17
18
|
|
|
18
19
|
} // namespace margelo::nitro::onnx::speech
|