react-native-nitro-onnx 0.1.0 → 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/NitroOnnxSpeech.podspec +3 -1
- package/README.md +146 -41
- package/android/CMakeLists.txt +54 -1
- package/android/build.gradle +6 -0
- 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 +26 -17
- package/cpp/AsrEngine.cpp +154 -59
- package/cpp/AsrEngine.hpp +19 -6
- package/cpp/AudioFileReader.cpp +4 -0
- package/cpp/ModelSingleton.hpp +14 -0
- package/cpp/NitroOnnxSpeech.cpp +37 -18
- package/cpp/NitroOnnxSpeech.hpp +1 -7
- package/cpp/OfflineAsr.cpp +17 -8
- 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 +20 -9
- package/cpp/StreamingAsr.hpp +1 -1
- package/cpp/Tts.cpp +44 -10
- package/cpp/Tts.hpp +2 -1
- package/cpp/TtsEngine.cpp +17 -9
- package/cpp/TtsEngine.hpp +24 -5
- package/cpp/Vad.cpp +12 -11
- package/cpp/Vad.hpp +1 -1
- package/cpp/VadEngine.cpp +27 -33
- package/cpp/VadEngine.hpp +9 -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/nitrogen/generated/shared/c++/AsrModelConfig.hpp +10 -2
- package/nitrogen/generated/shared/c++/HybridOnnxSpeechSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridOnnxSpeechSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/TtsModelConfig.hpp +10 -2
- package/nitrogen/generated/shared/c++/VadConfig.hpp +6 -2
- package/package.json +4 -3
- package/scripts/generate-version.js +22 -0
- package/src/specs/OnnxSpeech.nitro.ts +43 -6
- 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,24 +129,51 @@ 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
|
|
|
97
173
|
c.model_config.tokens = tokens.c_str();
|
|
98
174
|
c.model_config.num_threads = config_.numThreads;
|
|
99
|
-
c.model_config.debug = 0;
|
|
175
|
+
c.model_config.debug = config_.debug ? 1 : 0;
|
|
176
|
+
c.model_config.provider = config_.provider.c_str();
|
|
100
177
|
c.decoding_method = config_.decodingMethod.c_str();
|
|
101
178
|
c.max_active_paths = config_.maxActivePaths;
|
|
102
179
|
|
|
@@ -112,34 +189,34 @@ void OfflineAsrEngine::load(const AsrEngineConfig& config) {
|
|
|
112
189
|
}
|
|
113
190
|
|
|
114
191
|
bool OfflineAsrEngine::isLoaded() const {
|
|
192
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
115
193
|
return recognizer_ != nullptr;
|
|
116
194
|
}
|
|
117
195
|
|
|
118
196
|
AsrEngineResult OfflineAsrEngine::recognize(const std::vector<float>& samples) {
|
|
119
|
-
|
|
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) {
|
|
120
205
|
throw std::runtime_error("Offline ASR not loaded");
|
|
121
206
|
}
|
|
122
207
|
|
|
123
|
-
const SherpaOnnxOfflineStream* stream = SherpaOnnxCreateOfflineStream(
|
|
208
|
+
const SherpaOnnxOfflineStream* stream = SherpaOnnxCreateOfflineStream(recognizer.get());
|
|
124
209
|
SherpaOnnxAcceptWaveformOffline(stream, 16000, samples.data(), static_cast<int32_t>(samples.size()));
|
|
125
|
-
SherpaOnnxDecodeOfflineStream(
|
|
210
|
+
SherpaOnnxDecodeOfflineStream(recognizer.get(), stream);
|
|
126
211
|
|
|
127
|
-
const
|
|
212
|
+
const SherpaOnnxOfflineRecognizerResult* raw = SherpaOnnxGetOfflineStreamResult(stream);
|
|
128
213
|
AsrEngineResult result;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const char* textStart = std::strstr(result.json.c_str(), textKey);
|
|
135
|
-
if (textStart != nullptr) {
|
|
136
|
-
textStart += std::strlen(textKey);
|
|
137
|
-
const char* textEnd = std::strstr(textStart, "\"");
|
|
138
|
-
if (textEnd != nullptr) {
|
|
139
|
-
result.text = std::string(textStart, textEnd);
|
|
140
|
-
}
|
|
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()));
|
|
141
219
|
}
|
|
142
|
-
result.endMs = samplesToMs(static_cast<int32_t>(samples.size()));
|
|
143
220
|
|
|
144
221
|
SherpaOnnxDestroyOfflineStream(stream);
|
|
145
222
|
return result;
|
|
@@ -156,6 +233,7 @@ AsrEngineResult OfflineAsrEngine::recognizeFile(const std::string& path) {
|
|
|
156
233
|
}
|
|
157
234
|
|
|
158
235
|
void OfflineAsrEngine::unload() {
|
|
236
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
159
237
|
recognizer_.reset();
|
|
160
238
|
}
|
|
161
239
|
|
|
@@ -163,9 +241,6 @@ void OfflineAsrEngine::unload() {
|
|
|
163
241
|
// Streaming ASR
|
|
164
242
|
// ------------------------------------------------------------------------------
|
|
165
243
|
|
|
166
|
-
StreamingAsrEngine::StreamingAsrEngine(std::shared_ptr<ThreadPool> threadPool)
|
|
167
|
-
: threadPool_(std::move(threadPool)) {}
|
|
168
|
-
|
|
169
244
|
StreamingAsrEngine::~StreamingAsrEngine() {
|
|
170
245
|
unload();
|
|
171
246
|
}
|
|
@@ -174,10 +249,11 @@ void StreamingAsrEngine::load(
|
|
|
174
249
|
const AsrEngineConfig& config,
|
|
175
250
|
std::shared_ptr<StreamingAsrListener> listener) {
|
|
176
251
|
unload();
|
|
252
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
177
253
|
config_ = config;
|
|
178
254
|
listener_ = std::move(listener);
|
|
179
255
|
|
|
180
|
-
const std::string key = config_.
|
|
256
|
+
const std::string key = config_.cacheSignature() + "|streaming";
|
|
181
257
|
auto cached = gOnlineRecognizerCache.getOrCreate(key, [this](const std::string&) {
|
|
182
258
|
SherpaOnnxOnlineRecognizerConfig c;
|
|
183
259
|
std::memset(&c, 0, sizeof(c));
|
|
@@ -196,11 +272,13 @@ void StreamingAsrEngine::load(
|
|
|
196
272
|
c.model_config.transducer.joiner = joiner.c_str();
|
|
197
273
|
break;
|
|
198
274
|
default:
|
|
199
|
-
throw std::runtime_error("Streaming ASR
|
|
275
|
+
throw std::runtime_error("Streaming ASR only supports transducer / zipformer / conformer models");
|
|
200
276
|
}
|
|
201
277
|
|
|
202
278
|
c.model_config.tokens = tokens.c_str();
|
|
203
279
|
c.model_config.num_threads = config_.numThreads;
|
|
280
|
+
c.model_config.debug = config_.debug ? 1 : 0;
|
|
281
|
+
c.model_config.provider = config_.provider.c_str();
|
|
204
282
|
c.decoding_method = config_.decodingMethod.c_str();
|
|
205
283
|
c.max_active_paths = config_.maxActivePaths;
|
|
206
284
|
c.enable_endpoint = 1;
|
|
@@ -219,48 +297,64 @@ void StreamingAsrEngine::load(
|
|
|
219
297
|
}
|
|
220
298
|
|
|
221
299
|
bool StreamingAsrEngine::isLoaded() const {
|
|
300
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
222
301
|
return recognizer_ != nullptr && stream_ != nullptr;
|
|
223
302
|
}
|
|
224
303
|
|
|
225
304
|
void StreamingAsrEngine::acceptWaveform(const std::vector<float>& samples) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
}
|
|
240
324
|
}
|
|
325
|
+
listener = listener_.lock();
|
|
326
|
+
}
|
|
327
|
+
if (hasResult && listener) {
|
|
328
|
+
listener->onPartialResult(result);
|
|
241
329
|
}
|
|
242
330
|
}
|
|
243
331
|
|
|
244
332
|
AsrEngineResult StreamingAsrEngine::finalize() {
|
|
245
|
-
|
|
246
|
-
throw std::runtime_error("Streaming ASR not loaded");
|
|
247
|
-
}
|
|
248
|
-
SherpaOnnxOnlineStreamInputFinished(stream_.get());
|
|
249
|
-
SherpaOnnxDecodeOnlineStream(recognizer_.get(), stream_.get());
|
|
250
|
-
const char* json = SherpaOnnxGetOnlineStreamResultAsJson(recognizer_.get(), stream_.get());
|
|
333
|
+
std::shared_ptr<StreamingAsrListener> listener;
|
|
251
334
|
AsrEngineResult result;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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();
|
|
256
349
|
}
|
|
257
|
-
if (
|
|
258
|
-
|
|
350
|
+
if (listener) {
|
|
351
|
+
listener->onFinalResult(result);
|
|
259
352
|
}
|
|
260
353
|
return result;
|
|
261
354
|
}
|
|
262
355
|
|
|
263
356
|
void StreamingAsrEngine::reset() {
|
|
357
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
264
358
|
if (recognizer_ != nullptr) {
|
|
265
359
|
stream_ = std::shared_ptr<const SherpaOnnxOnlineStream>(
|
|
266
360
|
SherpaOnnxCreateOnlineStream(recognizer_.get()),
|
|
@@ -269,6 +363,7 @@ void StreamingAsrEngine::reset() {
|
|
|
269
363
|
}
|
|
270
364
|
|
|
271
365
|
void StreamingAsrEngine::unload() {
|
|
366
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
272
367
|
stream_.reset();
|
|
273
368
|
recognizer_.reset();
|
|
274
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
|
|
|
@@ -41,6 +41,19 @@ struct AsrEngineConfig {
|
|
|
41
41
|
int32_t maxActivePaths = 4;
|
|
42
42
|
std::string language = "en";
|
|
43
43
|
bool useItn = true;
|
|
44
|
+
bool debug = false;
|
|
45
|
+
#if defined(__ANDROID__) && defined(SHERPA_ONNX_ENABLE_QNN)
|
|
46
|
+
std::string provider = "qnn";
|
|
47
|
+
#elif defined(__ANDROID__)
|
|
48
|
+
std::string provider = "nnapi";
|
|
49
|
+
#elif defined(__APPLE__)
|
|
50
|
+
std::string provider = "coreml";
|
|
51
|
+
#else
|
|
52
|
+
std::string provider = "cpu";
|
|
53
|
+
#endif
|
|
54
|
+
|
|
55
|
+
/** Cache-key signature covering options that change recognizer identity. */
|
|
56
|
+
std::string cacheSignature() const;
|
|
44
57
|
};
|
|
45
58
|
|
|
46
59
|
/** Native recognition result before conversion to the generated AsrResult. */
|
|
@@ -65,7 +78,7 @@ class StreamingAsrListener {
|
|
|
65
78
|
/** Offline ASR engine. */
|
|
66
79
|
class OfflineAsrEngine final {
|
|
67
80
|
public:
|
|
68
|
-
|
|
81
|
+
OfflineAsrEngine() = default;
|
|
69
82
|
~OfflineAsrEngine();
|
|
70
83
|
|
|
71
84
|
OfflineAsrEngine(const OfflineAsrEngine&) = delete;
|
|
@@ -78,7 +91,7 @@ class OfflineAsrEngine final {
|
|
|
78
91
|
void unload();
|
|
79
92
|
|
|
80
93
|
private:
|
|
81
|
-
std::
|
|
94
|
+
mutable std::mutex mutex_;
|
|
82
95
|
AsrEngineConfig config_;
|
|
83
96
|
std::shared_ptr<const SherpaOnnxOfflineRecognizer> recognizer_;
|
|
84
97
|
};
|
|
@@ -86,7 +99,7 @@ class OfflineAsrEngine final {
|
|
|
86
99
|
/** Streaming ASR engine. */
|
|
87
100
|
class StreamingAsrEngine final {
|
|
88
101
|
public:
|
|
89
|
-
|
|
102
|
+
StreamingAsrEngine() = default;
|
|
90
103
|
~StreamingAsrEngine();
|
|
91
104
|
|
|
92
105
|
StreamingAsrEngine(const StreamingAsrEngine&) = delete;
|
|
@@ -100,9 +113,9 @@ class StreamingAsrEngine final {
|
|
|
100
113
|
void unload();
|
|
101
114
|
|
|
102
115
|
private:
|
|
103
|
-
std::
|
|
116
|
+
mutable std::mutex mutex_;
|
|
104
117
|
AsrEngineConfig config_;
|
|
105
|
-
std::
|
|
118
|
+
std::weak_ptr<StreamingAsrListener> listener_;
|
|
106
119
|
std::shared_ptr<const SherpaOnnxOnlineRecognizer> recognizer_;
|
|
107
120
|
std::shared_ptr<const SherpaOnnxOnlineStream> stream_;
|
|
108
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,60 +9,79 @@
|
|
|
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>
|
|
15
16
|
|
|
17
|
+
#ifdef __ANDROID__
|
|
18
|
+
#include <sys/system_properties.h>
|
|
19
|
+
#endif
|
|
20
|
+
|
|
16
21
|
namespace margelo::nitro::onnx::speech {
|
|
17
22
|
|
|
18
23
|
NitroOnnxSpeech::NitroOnnxSpeech()
|
|
19
|
-
: HybridObject(TAG)
|
|
20
|
-
threadPool_(std::make_shared<ThreadPool>()),
|
|
21
|
-
cacheDir_(getCacheDir()) {}
|
|
24
|
+
: HybridObject(TAG) {}
|
|
22
25
|
|
|
23
26
|
NitroOnnxSpeech::~NitroOnnxSpeech() = default;
|
|
24
27
|
|
|
25
28
|
std::shared_ptr<HybridVadSpec> NitroOnnxSpeech::createVad() {
|
|
26
|
-
return std::make_shared<Vad>(
|
|
29
|
+
return std::make_shared<Vad>();
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
std::shared_ptr<HybridOfflineAsrSpec> NitroOnnxSpeech::createOfflineAsr() {
|
|
30
|
-
return std::make_shared<OfflineAsr>(
|
|
33
|
+
return std::make_shared<OfflineAsr>();
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
std::shared_ptr<HybridStreamingAsrSpec> NitroOnnxSpeech::createStreamingAsr() {
|
|
34
|
-
return std::make_shared<StreamingAsr>(
|
|
37
|
+
return std::make_shared<StreamingAsr>();
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
std::shared_ptr<HybridTtsSpec> NitroOnnxSpeech::createTts() {
|
|
38
|
-
return std::make_shared<Tts>(
|
|
41
|
+
return std::make_shared<Tts>();
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
std::shared_ptr<HybridSpeakerManagerSpec> NitroOnnxSpeech::createSpeakerManager() {
|
|
42
|
-
return std::make_shared<SpeakerManager>(
|
|
45
|
+
return std::make_shared<SpeakerManager>();
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
std::string NitroOnnxSpeech::getVersion() {
|
|
46
|
-
return
|
|
49
|
+
return NITRO_ONNX_SPEECH_VERSION;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
std::string NitroOnnxSpeech::getQualcommSoc() {
|
|
50
53
|
#ifdef __ANDROID__
|
|
54
|
+
// 1) Try ro.soc.model first (available on most modern Android devices).
|
|
55
|
+
char prop[PROP_VALUE_MAX] = {0};
|
|
56
|
+
if (__system_property_get("ro.soc.model", prop) > 0 && prop[0] != '\0') {
|
|
57
|
+
return std::string(prop);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 2) Fall back to parsing /proc/cpuinfo Hardware line.
|
|
51
61
|
FILE* f = fopen("/proc/cpuinfo", "r");
|
|
52
|
-
if (!f) return
|
|
62
|
+
if (!f) return "";
|
|
53
63
|
char line[256];
|
|
54
|
-
bool found = false;
|
|
55
64
|
while (fgets(line, sizeof(line), f)) {
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
break;
|
|
65
|
+
if (strncmp(line, "Hardware", 8) == 0) {
|
|
66
|
+
char* colon = strchr(line, ':');
|
|
67
|
+
if (!colon) break;
|
|
68
|
+
char* val = colon + 1;
|
|
69
|
+
while (*val == ' ' || *val == '\t') ++val;
|
|
70
|
+
char* nl = strchr(val, '\n');
|
|
71
|
+
if (nl) *nl = '\0';
|
|
72
|
+
if (strstr(val, "Qualcomm") == nullptr) break;
|
|
73
|
+
const char* last = strrchr(val, ' ');
|
|
74
|
+
if (last && *(last + 1) != '\0') {
|
|
75
|
+
fclose(f);
|
|
76
|
+
return std::string(last + 1);
|
|
77
|
+
}
|
|
78
|
+
fclose(f);
|
|
79
|
+
return std::string(val);
|
|
59
80
|
}
|
|
60
81
|
}
|
|
61
82
|
fclose(f);
|
|
62
|
-
return found;
|
|
63
|
-
#else
|
|
64
|
-
return false;
|
|
65
83
|
#endif
|
|
84
|
+
return "";
|
|
66
85
|
}
|
|
67
86
|
|
|
68
87
|
} // namespace margelo::nitro::onnx::speech
|
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>
|
|
@@ -32,11 +30,7 @@ class NitroOnnxSpeech : public HybridOnnxSpeechSpec {
|
|
|
32
30
|
std::shared_ptr<HybridTtsSpec> createTts() override;
|
|
33
31
|
std::shared_ptr<HybridSpeakerManagerSpec> createSpeakerManager() override;
|
|
34
32
|
std::string getVersion() override;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
private:
|
|
38
|
-
std::shared_ptr<ThreadPool> threadPool_;
|
|
39
|
-
std::string cacheDir_;
|
|
33
|
+
std::string getQualcommSoc() override;
|
|
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;
|
|
@@ -53,7 +52,17 @@ std::shared_ptr<Promise<void>> OfflineAsr::load(const AsrModelConfig& config) {
|
|
|
53
52
|
native.maxActivePaths = static_cast<int32_t>(config.maxActivePaths.value_or(4));
|
|
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);
|
|
56
|
+
#if defined(__ANDROID__) && defined(SHERPA_ONNX_ENABLE_QNN)
|
|
57
|
+
native.provider = config.provider.value_or("qnn");
|
|
58
|
+
#elif defined(__ANDROID__)
|
|
59
|
+
native.provider = config.provider.value_or("nnapi");
|
|
60
|
+
#elif defined(__APPLE__)
|
|
61
|
+
native.provider = config.provider.value_or("coreml");
|
|
62
|
+
#else
|
|
63
|
+
native.provider = config.provider.value_or("cpu");
|
|
64
|
+
#endif
|
|
65
|
+
self->engine_.load(native);
|
|
57
66
|
});
|
|
58
67
|
}
|
|
59
68
|
|
|
@@ -63,19 +72,19 @@ bool OfflineAsr::isLoaded() {
|
|
|
63
72
|
|
|
64
73
|
std::shared_ptr<Promise<AsrResult>> OfflineAsr::recognize(
|
|
65
74
|
const std::shared_ptr<ArrayBuffer>& samples) {
|
|
66
|
-
return Promise<AsrResult>::async([
|
|
75
|
+
return Promise<AsrResult>::async([self = shared_cast<OfflineAsr>(), samples]() {
|
|
67
76
|
auto floatSamples = bytesToFloatVector(samples->data(), samples->size());
|
|
68
|
-
return toAsrResult(engine_.recognize(floatSamples));
|
|
77
|
+
return toAsrResult(self->engine_.recognize(floatSamples));
|
|
69
78
|
});
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
std::shared_ptr<Promise<AsrResult>> OfflineAsr::recognizeFile(const std::string& path) {
|
|
73
82
|
return Promise<AsrResult>::async(
|
|
74
|
-
[
|
|
83
|
+
[self = shared_cast<OfflineAsr>(), path]() { return toAsrResult(self->engine_.recognizeFile(path)); });
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
std::shared_ptr<Promise<void>> OfflineAsr::unload() {
|
|
78
|
-
return Promise<void>::async([
|
|
87
|
+
return Promise<void>::async([self = shared_cast<OfflineAsr>()]() { self->engine_.unload(); });
|
|
79
88
|
}
|
|
80
89
|
|
|
81
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;
|