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.
Files changed (40) hide show
  1. package/README.md +101 -31
  2. package/android/CMakeLists.txt +5 -3
  3. package/android/src/main/AndroidManifest.xml +0 -2
  4. package/android/src/main/cpp/cpp-adapter.cpp +2 -2
  5. package/android/src/main/java/com/margelo/nitro/onnx/speech/OnnxSpeechPackage.kt +24 -15
  6. package/cpp/AsrEngine.cpp +150 -58
  7. package/cpp/AsrEngine.hpp +12 -7
  8. package/cpp/AudioFileReader.cpp +4 -0
  9. package/cpp/ModelSingleton.hpp +14 -0
  10. package/cpp/NitroOnnxSpeech.cpp +8 -9
  11. package/cpp/NitroOnnxSpeech.hpp +0 -6
  12. package/cpp/OfflineAsr.cpp +9 -12
  13. package/cpp/OfflineAsr.hpp +1 -1
  14. package/cpp/ResourceDir.cpp +5 -5
  15. package/cpp/ResourceDir.hpp +5 -4
  16. package/cpp/SpeakerEngine.cpp +38 -28
  17. package/cpp/SpeakerEngine.hpp +14 -13
  18. package/cpp/SpeakerManager.cpp +16 -14
  19. package/cpp/SpeakerManager.hpp +2 -1
  20. package/cpp/SpeakerRecord.cpp +125 -0
  21. package/cpp/SpeakerRecord.hpp +42 -0
  22. package/cpp/StreamingAsr.cpp +13 -10
  23. package/cpp/StreamingAsr.hpp +1 -1
  24. package/cpp/Tts.cpp +38 -10
  25. package/cpp/Tts.hpp +2 -1
  26. package/cpp/TtsEngine.cpp +15 -7
  27. package/cpp/TtsEngine.hpp +18 -5
  28. package/cpp/Vad.cpp +11 -11
  29. package/cpp/Vad.hpp +1 -1
  30. package/cpp/VadEngine.cpp +26 -33
  31. package/cpp/VadEngine.hpp +8 -10
  32. package/cpp/Version.hpp +7 -0
  33. package/ios/OnnxSpeechInitializer.mm +18 -6
  34. package/lib/specs/OnnxSpeech.nitro.d.ts +43 -6
  35. package/lib/specs/OnnxSpeech.nitro.d.ts.map +1 -1
  36. package/package.json +4 -3
  37. package/scripts/generate-version.js +22 -0
  38. package/src/specs/OnnxSpeech.nitro.ts +25 -8
  39. package/cpp/ThreadPool.cpp +0 -41
  40. 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
- const std::string key = config_.modelDir + "|" + std::to_string(static_cast<int>(config_.type));
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.paraformer.model = model.c_str();
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
- if (config_.type == AsrModelType::NEMO) {
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
- if (recognizer_ == nullptr) {
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(recognizer_.get());
208
+ const SherpaOnnxOfflineStream* stream = SherpaOnnxCreateOfflineStream(recognizer.get());
125
209
  SherpaOnnxAcceptWaveformOffline(stream, 16000, samples.data(), static_cast<int32_t>(samples.size()));
126
- SherpaOnnxDecodeOfflineStream(recognizer_.get(), stream);
210
+ SherpaOnnxDecodeOfflineStream(recognizer.get(), stream);
127
211
 
128
- const char* json = SherpaOnnxGetOfflineStreamResultAsJson(stream);
212
+ const SherpaOnnxOfflineRecognizerResult* raw = SherpaOnnxGetOfflineStreamResult(stream);
129
213
  AsrEngineResult result;
130
- result.json = json ? json : "";
131
-
132
- // Parse the JSON to extract text. In a full implementation, use a JSON
133
- // library to also populate timestamps and score.
134
- const char* textKey = "\"text\":\"";
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_.modelDir + "|streaming|" + std::to_string(static_cast<int>(config_.type));
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 does not support this model type in the scaffold");
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
- if (recognizer_ == nullptr || stream_ == nullptr) {
230
- throw std::runtime_error("Streaming ASR not loaded");
231
- }
232
- SherpaOnnxOnlineStreamAcceptWaveform(stream_.get(), 16000, samples.data(), static_cast<int32_t>(samples.size()));
233
-
234
- if (listener_ && SherpaOnnxIsOnlineStreamReady(recognizer_.get(), stream_.get())) {
235
- SherpaOnnxDecodeOnlineStream(recognizer_.get(), stream_.get());
236
- const char* json = SherpaOnnxGetOnlineStreamResultAsJson(recognizer_.get(), stream_.get());
237
- if (json != nullptr) {
238
- AsrEngineResult result;
239
- result.json = json;
240
- // TODO: parse text from JSON.
241
- listener_->onPartialResult(result);
242
- SherpaOnnxDestroyOnlineStreamResultJson(json);
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
- if (recognizer_ == nullptr || stream_ == nullptr) {
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
- if (json != nullptr) {
256
- result.json = json;
257
- // TODO: parse text from JSON.
258
- SherpaOnnxDestroyOnlineStreamResultJson(json);
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 (listener_) {
261
- listener_->onFinalResult(result);
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
- #ifdef __ANDROID__
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
- explicit OfflineAsrEngine(std::shared_ptr<ThreadPool> threadPool);
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::shared_ptr<ThreadPool> threadPool_;
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
- explicit StreamingAsrEngine(std::shared_ptr<ThreadPool> threadPool);
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::shared_ptr<ThreadPool> threadPool_;
116
+ mutable std::mutex mutex_;
112
117
  AsrEngineConfig config_;
113
- std::shared_ptr<StreamingAsrListener> listener_;
118
+ std::weak_ptr<StreamingAsrListener> listener_;
114
119
  std::shared_ptr<const SherpaOnnxOnlineRecognizer> recognizer_;
115
120
  std::shared_ptr<const SherpaOnnxOnlineStream> stream_;
116
121
  };
@@ -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 {
@@ -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
  };
@@ -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>(threadPool_);
29
+ return std::make_shared<Vad>();
31
30
  }
32
31
 
33
32
  std::shared_ptr<HybridOfflineAsrSpec> NitroOnnxSpeech::createOfflineAsr() {
34
- return std::make_shared<OfflineAsr>(threadPool_);
33
+ return std::make_shared<OfflineAsr>();
35
34
  }
36
35
 
37
36
  std::shared_ptr<HybridStreamingAsrSpec> NitroOnnxSpeech::createStreamingAsr() {
38
- return std::make_shared<StreamingAsr>(threadPool_);
37
+ return std::make_shared<StreamingAsr>();
39
38
  }
40
39
 
41
40
  std::shared_ptr<HybridTtsSpec> NitroOnnxSpeech::createTts() {
42
- return std::make_shared<Tts>(threadPool_);
41
+ return std::make_shared<Tts>();
43
42
  }
44
43
 
45
44
  std::shared_ptr<HybridSpeakerManagerSpec> NitroOnnxSpeech::createSpeakerManager() {
46
- return std::make_shared<SpeakerManager>(threadPool_, cacheDir_);
45
+ return std::make_shared<SpeakerManager>();
47
46
  }
48
47
 
49
48
  std::string NitroOnnxSpeech::getVersion() {
50
- return "0.1.0";
49
+ return NITRO_ONNX_SPEECH_VERSION;
51
50
  }
52
51
 
53
52
  std::string NitroOnnxSpeech::getQualcommSoc() {
@@ -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
@@ -28,15 +28,14 @@ AsrResult toAsrResult(const AsrEngineResult& native) {
28
28
 
29
29
  } // namespace
30
30
 
31
- OfflineAsr::OfflineAsr(std::shared_ptr<ThreadPool> threadPool)
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([this, config]() {
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
- #ifdef __ANDROID__
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
- #else
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([this, samples]() {
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
- [this, path]() { return toAsrResult(engine_.recognizeFile(path)); });
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([this]() { engine_.unload(); });
87
+ return Promise<void>::async([self = shared_cast<OfflineAsr>()]() { self->engine_.unload(); });
91
88
  }
92
89
 
93
90
  } // namespace margelo::nitro::onnx::speech
@@ -17,7 +17,7 @@ class OfflineAsr : public HybridOfflineAsrSpec {
17
17
  public:
18
18
  static constexpr auto TAG = "OfflineAsr";
19
19
 
20
- explicit OfflineAsr(std::shared_ptr<ThreadPool> threadPool);
20
+ OfflineAsr();
21
21
  ~OfflineAsr() override;
22
22
 
23
23
  std::shared_ptr<Promise<void>> load(const AsrModelConfig& config) override;
@@ -7,7 +7,7 @@ namespace margelo::nitro::onnx::speech {
7
7
 
8
8
  namespace {
9
9
  std::string g_resourceDir;
10
- std::string g_cacheDir;
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& getCacheDir() {
22
- return g_cacheDir;
21
+ const std::string& getDocumentDir() {
22
+ return g_documentDir;
23
23
  }
24
24
 
25
- void setCacheDir(const std::string& dir) {
26
- g_cacheDir = dir;
25
+ void setDocumentDir(const std::string& dir) {
26
+ g_documentDir = dir;
27
27
  }
28
28
 
29
29
  } // namespace margelo::nitro::onnx::speech
@@ -1,7 +1,8 @@
1
1
  // ------------------------------------------------------------------------------
2
2
  // ResourceDir.hpp
3
- // Process-wide directories for bundled model files and persistent cache.
4
- // Set once from the platform layer at startup (JNI on Android, ObjC++ on iOS).
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& getCacheDir();
16
- void setCacheDir(const std::string& dir);
16
+ const std::string& getDocumentDir();
17
+ void setDocumentDir(const std::string& dir);
17
18
 
18
19
  } // namespace margelo::nitro::onnx::speech