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.
Files changed (47) hide show
  1. package/NitroOnnxSpeech.podspec +3 -1
  2. package/README.md +146 -41
  3. package/android/CMakeLists.txt +54 -1
  4. package/android/build.gradle +6 -0
  5. package/android/src/main/AndroidManifest.xml +0 -2
  6. package/android/src/main/cpp/cpp-adapter.cpp +2 -2
  7. package/android/src/main/java/com/margelo/nitro/onnx/speech/OnnxSpeechPackage.kt +26 -17
  8. package/cpp/AsrEngine.cpp +154 -59
  9. package/cpp/AsrEngine.hpp +19 -6
  10. package/cpp/AudioFileReader.cpp +4 -0
  11. package/cpp/ModelSingleton.hpp +14 -0
  12. package/cpp/NitroOnnxSpeech.cpp +37 -18
  13. package/cpp/NitroOnnxSpeech.hpp +1 -7
  14. package/cpp/OfflineAsr.cpp +17 -8
  15. package/cpp/OfflineAsr.hpp +1 -1
  16. package/cpp/ResourceDir.cpp +5 -5
  17. package/cpp/ResourceDir.hpp +5 -4
  18. package/cpp/SpeakerEngine.cpp +38 -28
  19. package/cpp/SpeakerEngine.hpp +14 -13
  20. package/cpp/SpeakerManager.cpp +16 -14
  21. package/cpp/SpeakerManager.hpp +2 -1
  22. package/cpp/SpeakerRecord.cpp +125 -0
  23. package/cpp/SpeakerRecord.hpp +42 -0
  24. package/cpp/StreamingAsr.cpp +20 -9
  25. package/cpp/StreamingAsr.hpp +1 -1
  26. package/cpp/Tts.cpp +44 -10
  27. package/cpp/Tts.hpp +2 -1
  28. package/cpp/TtsEngine.cpp +17 -9
  29. package/cpp/TtsEngine.hpp +24 -5
  30. package/cpp/Vad.cpp +12 -11
  31. package/cpp/Vad.hpp +1 -1
  32. package/cpp/VadEngine.cpp +27 -33
  33. package/cpp/VadEngine.hpp +9 -10
  34. package/cpp/Version.hpp +7 -0
  35. package/ios/OnnxSpeechInitializer.mm +18 -6
  36. package/lib/specs/OnnxSpeech.nitro.d.ts +43 -6
  37. package/lib/specs/OnnxSpeech.nitro.d.ts.map +1 -1
  38. package/nitrogen/generated/shared/c++/AsrModelConfig.hpp +10 -2
  39. package/nitrogen/generated/shared/c++/HybridOnnxSpeechSpec.cpp +1 -1
  40. package/nitrogen/generated/shared/c++/HybridOnnxSpeechSpec.hpp +1 -1
  41. package/nitrogen/generated/shared/c++/TtsModelConfig.hpp +10 -2
  42. package/nitrogen/generated/shared/c++/VadConfig.hpp +6 -2
  43. package/package.json +4 -3
  44. package/scripts/generate-version.js +22 -0
  45. package/src/specs/OnnxSpeech.nitro.ts +43 -6
  46. package/cpp/ThreadPool.cpp +0 -41
  47. package/cpp/ThreadPool.hpp +0 -62
@@ -41,9 +41,11 @@ Pod::Spec.new do |s|
41
41
  # to match the inner framework name (SherpaOnnxC.framework), which CocoaPods
42
42
  # requires for correct linker flag generation.
43
43
  s.vendored_frameworks = "cpp/sherpa-onnx-prebuilt/ios/SherpaOnnxC.xcframework"
44
+ puts "[NitroOnnxSpeech] πŸ”§ CoreML execution provider enabled for iOS"
44
45
  s.pod_target_xcconfig = {
45
46
  "HEADER_SEARCH_PATHS" => '"$(PODS_TARGET_SRCROOT)/cpp/sherpa-onnx-prebuilt/include" "$(PODS_TARGET_SRCROOT)/cpp" "$(PODS_TARGET_SRCROOT)/nitrogen/generated/ios"',
46
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++20"
47
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
48
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) SHERPA_ONNX_ENABLE_COREML=1"
47
49
  }
48
50
 
49
51
  load 'nitrogen/generated/ios/NitroOnnxSpeech+autolinking.rb'
package/README.md CHANGED
@@ -9,7 +9,22 @@ A React Native [Nitro Module](https://nitro.margelo.com) that wraps [sherpa-onnx
9
9
 
10
10
  All audio I/O uses zero-copy `ArrayBuffer` with **16 kHz mono f32 PCM**.
11
11
 
12
- > **Note:** This repository is a structural scaffold. Every model family has a typed config slot and a singleton-backed engine. Only one model per category is fully wired in the reference implementation; the remaining model types map to the correct sherpa-onnx C API fields and are ready for incremental completion.
12
+ > **Note:** Every ASR / TTS model type is wired to the corresponding sherpa-onnx C API config segment. Coverage is complete at the binding layer; per-model quality still depends on the downloaded sherpa-onnx model files.
13
+
14
+ > πŸ“’ **Important note about scope & package name**
15
+ >
16
+ > At the present time, this binding is built exclusively for **speech‑related workloads via sherpa‑onnx**:
17
+ > ASR, TTS, VAD and speaker embedding only.
18
+ > It is **NOT a general‑purpose ONNX Runtime binding** for arbitrary ONNX models (YOLO, LLM etc).
19
+ >
20
+ > The package name `react-native-nitro‑onnx` may appear to imply general‑purpose ONNX support,
21
+ > but that is not the current goal of this repository.
22
+ >
23
+ > If you are an open‑source developer and would like to take over this npm package name
24
+ > to build a truly general‑purpose Nitro ONNX binding supporting LLM / CV workloads,
25
+ > feel free to open a GitHub issue to contact me for discussion about npm ownership transfer.
26
+ >
27
+ > For now this repo will continue focusing on the speech‑only sherpa‑onnx use‑case.
13
28
 
14
29
  ## Table of Contents
15
30
 
@@ -22,7 +37,7 @@ All audio I/O uses zero-copy `ArrayBuffer` with **16 kHz mono f32 PCM**.
22
37
  - [Model File Requirements](#model-file-requirements)
23
38
  - [Installation](#installation)
24
39
  - [Usage](#usage)
25
- - [Qualcomm CPU Detection](#qualcomm-cpu-detection)
40
+ - [Execution Providers](#execution-providers)
26
41
  - [VAD Pre-buffer](#vad-pre-buffer)
27
42
  - [Voice Cloning](#voice-cloning)
28
43
  - [Threading](#threading)
@@ -34,12 +49,14 @@ All audio I/O uses zero-copy `ArrayBuffer` with **16 kHz mono f32 PCM**.
34
49
  ```
35
50
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
36
51
  β”‚ JS / TS β”‚
37
- β”‚ getOnnxSpeech() β†’ createVad() / createTts() / createAsr() β”‚
52
+ β”‚ getOnnxSpeech() β†’ createVad() / createTts() / β”‚
53
+ β”‚ createOfflineAsr() / createStreamingAsr() β”‚
38
54
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
39
55
  β”‚ react-native-nitro-modules (zero-copy ArrayBuffer)
40
56
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
41
57
  β”‚ C++ β”‚
42
- β”‚ OnnxSpeechImpl β†’ VadEngine / AsrEngine / TtsEngine / ... β”‚
58
+ β”‚ NitroOnnxSpeech β†’ Vad / OfflineAsr / StreamingAsr / Tts / β”‚
59
+ β”‚ SpeakerManager β”‚
43
60
  β”‚ ModelSingleton caches heavy recognizer / TTS instances β”‚
44
61
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
45
62
  β”‚ sherpa-onnx C API
@@ -50,10 +67,11 @@ All audio I/O uses zero-copy `ArrayBuffer` with **16 kHz mono f32 PCM**.
50
67
 
51
68
  Key design decisions:
52
69
 
53
- - **Singleton preloading:** `OfflineAsrEngine`, `StreamingAsrEngine`, `TtsEngine` and `SpeakerEngine` use `ModelSingleton` keyed by model directory and type. Loading the same model twice returns the same native instance.
54
- - **Background inference:** Every heavy operation runs on a fixed `ThreadPool` so the JS thread never blocks.
70
+ - **Singleton preloading:** `OfflineAsrEngine`, `StreamingAsrEngine`, `TtsEngine` and `SpeakerEngine` use `ModelSingleton`. The cache key covers model dir, type, provider, thread count and other identity-relevant options, so loading the same configuration twice returns the same native instance while a changed option creates a new one.
71
+ - **Background inference:** Every heavy operation runs on a background task pool provided by `react-native-nitro-modules` (`Promise::async`) so the JS thread never blocks. VAD additionally uses a dedicated processor thread for streaming segmentation.
55
72
  - **External model download:** The module does not bundle an internal downloader. Download model files in the background with a library such as [`@kesha-antonov/react-native-background-downloader`](https://github.com/kesha-antonov/react-native-background-downloader), then pass the local file paths to `load()` / `initialize()`.
56
73
  - **Zero-copy audio:** `ArrayBuffer` is the only audio transport format; samples are expected to be 16 kHz mono little-endian f32 PCM.
74
+ - **Storage:** Bundled assets (e.g. `silero_vad.onnx`) live in the platform resource dir. Registered speakers are written under the app document dir (`Application Support` on iOS, excluded from iCloud backup; `filesDir` on Android) at `<documentDir>/speakers/`.
57
75
 
58
76
  ## Supported Models
59
77
 
@@ -68,9 +86,9 @@ Key design decisions:
68
86
  | Conformer | `conformer` | attention-convolution | Streaming accuracy | Yes | Transducer triple |
69
87
  | Wenet | `wenet` | U2++ / CTC | Chinese industrial | No | Single `model.onnx` |
70
88
  | Telespeech | `telespeech` | telephony ASR | 8 kHz telco audio | No | Single `model.onnx` |
71
- | Moonshine | `moonshine` | lightweight | Edge devices | No | Single `model.onnx` |
89
+ | Moonshine | `moonshine` | lightweight encoder-decoder | Edge devices | No | `preprocessor.onnx` + `encoder.onnx` + decoder pair (see below) |
72
90
  | Dolphin | `dolphin` | CTC | English | No | Single `model.onnx` |
73
- | NeMo | `nemo` | CTC / RNNT | NVIDIA NeMo exported models | No | `model.onnx` + config |
91
+ | NeMo | `nemo` | CTC | NVIDIA NeMo exported models | No | `model.onnx` + tokens |
74
92
  | SenseVoice | `sense_voice` | multilingual | Alibaba SenseVoice | No | Single `model.onnx` |
75
93
 
76
94
  ### TTS
@@ -80,8 +98,8 @@ Key design decisions:
80
98
  | Kokoro | `kokoro` | internal | High, multi-speaker | Medium | Needs `model.onnx`, `voices.bin`, `tokens.txt`, `lexicon.txt` |
81
99
  | VITS | `vits` | internal | High quality | Medium | Needs `model.onnx`, `tokens.txt`, optional lexicon |
82
100
  | Matcha | `matcha` | external (e.g. Hifigan) | Fast, natural | Fast | Needs acoustic model + vocoder ONNX |
83
- | Pocket | `pocket` | internal | Lightweight zero-shot | Very fast | Needs `model.onnx` + config JSON |
84
- | ZipVoice | `zipvoice` | internal | Placeholder type | - | Maps to VITS-like config until sherpa-onnx exposes dedicated ZipVoice support |
101
+ | Pocket | `pocket` | internal | Lightweight zero-shot | Very fast | Multi-file (lm / encoder / decoder, see below) |
102
+ | ZipVoice | `zipvoice` | internal | Dedicated encoder/decoder | Fast | Needs `zipvoiceEncoder`, `zipvoiceDecoder`, vocoder, tokens |
85
103
 
86
104
  ### VAD
87
105
 
@@ -119,7 +137,7 @@ transducer/
119
137
  tokens.txt
120
138
  ```
121
139
 
122
- ### Paraformer / Wenet / Telespeech / Moonshine / Dolphin / SenseVoice (offline ASR)
140
+ ### Paraformer / Telespeech / Dolphin / SenseVoice (offline ASR)
123
141
 
124
142
  ```
125
143
  model/
@@ -127,12 +145,32 @@ model/
127
145
  tokens.txt
128
146
  ```
129
147
 
148
+ ### Wenet (offline ASR)
149
+
150
+ ```
151
+ wenet/
152
+ model.onnx
153
+ tokens.txt
154
+ ```
155
+
156
+ ### Moonshine (offline ASR)
157
+
158
+ ```
159
+ moonshine/
160
+ preprocessor.onnx β†’ model
161
+ encoder.onnx β†’ encoder
162
+ uncached_decoder.onnx β†’ decoder
163
+ cached_decoder.onnx β†’ joiner
164
+ tokens.txt
165
+ ```
166
+
167
+ Alternatively, pass `merged_decoder.onnx` as `decoder` and leave `joiner` unset.
168
+
130
169
  ### NeMo (offline ASR)
131
170
 
132
171
  ```
133
172
  nemo/
134
173
  model.onnx
135
- config.yaml
136
174
  tokens.txt
137
175
  ```
138
176
 
@@ -169,8 +207,27 @@ matcha/
169
207
 
170
208
  ```
171
209
  pocket/
172
- model.onnx
173
- config.json
210
+ lm_main.onnx
211
+ lm_flow.onnx
212
+ encoder.onnx
213
+ decoder.onnx
214
+ text_conditioner.onnx
215
+ vocab.json
216
+ token_scores.json
217
+ ```
218
+
219
+ Map these to `lmMain`, `lmFlow`, `pocketEncoder`, `pocketDecoder`, `textConditioner`, `vocabJson`, `tokenScoresJson`.
220
+
221
+ ### ZipVoice (TTS)
222
+
223
+ ```
224
+ zipvoice/
225
+ encoder.onnx β†’ zipvoiceEncoder
226
+ decoder.onnx β†’ zipvoiceDecoder
227
+ vocoder.onnx β†’ vocoder
228
+ tokens.txt
229
+ lexicon.txt
230
+ espeak-ng-data/ (optional)
174
231
  ```
175
232
 
176
233
  ### Silero VAD
@@ -197,7 +254,7 @@ Build requirements:
197
254
  - React Native >= 0.78
198
255
  - react-native-nitro-modules >= 0.35.8
199
256
  - Xcode 15 / Android NDK 26
200
- - The `prepare-sherpa-onnx.js` postinstall script downloads the sherpa-onnx
257
+ - The `postinstall` script runs `generate-version.js` (keeps `cpp/Version.hpp` in sync with `package.json`) and `prepare-sherpa-onnx.js`, which downloads the sherpa-onnx
201
258
  prebuilt tree (host static libraries, Android shared libraries, iOS
202
259
  xcframework, and C API headers) into `cpp/sherpa-onnx-prebuilt`.
203
260
 
@@ -304,32 +361,68 @@ await tts.saveWav(audio, "/path/to/output.wav");
304
361
  > source.start();
305
362
  > ```
306
363
 
307
- ## Qualcomm CPU Detection
364
+ ## Execution Providers
365
+
366
+ By default, the module selects a platform-appropriate execution provider:
308
367
 
309
- Use `isQualcommCpu()` to detect Qualcomm chipsets and select optimized QNN execution providers:
368
+ - **Android (no QNN SDK):** `nnapi` β€” NNAPI with CPU fallback.
369
+ - **Android (built with `QNN_ROOT`):** `qnn` β€” Qualcomm HTP via QNN; unsupported operators fall back to CPU.
370
+ - **iOS:** `coreml` β€” Apple Neural Engine via CoreML; unsupported operators fall back to CPU.
371
+
372
+ To disable NPU acceleration and force CPU-only inference, pass `provider: "cpu"` explicitly:
373
+
374
+ ```typescript
375
+ await asr.load({
376
+ type: "whisper",
377
+ // ... other config
378
+ provider: "cpu",
379
+ });
380
+ ```
381
+
382
+ ### Qualcomm SoC Detection
383
+
384
+ Use `getQualcommSoc()` to detect Qualcomm chipsets. Returns the SoC model string (e.g. `"SM8550"`, `"SM8650"`) on Qualcomm Android devices, or an empty string on iOS and non-Qualcomm chips.
385
+
386
+ On Android, it first reads the `ro.soc.model` system property, then falls back to parsing `/proc/cpuinfo`.
310
387
 
311
388
  ```typescript
312
389
  const speech = getOnnxSpeech();
313
- const isQualcomm = speech.isQualcommCpu();
314
-
315
- if (isQualcomm) {
316
- // Use QNN execution provider for better performance on Qualcomm chips
317
- await asr.load({
318
- type: "whisper",
319
- // ... other config
320
- provider: "qnn",
321
- });
390
+ const soc = speech.getQualcommSoc();
391
+
392
+ if (soc) {
393
+ console.log(`Qualcomm SoC: ${soc}`);
394
+ // Use QNN when the app was built with -DQNN_ROOT=...; otherwise NNAPI.
395
+ await asr.load({ type: "whisper", /* ... */ });
322
396
  } else {
323
- // Use CPU or other execution provider
324
- await asr.load({
325
- type: "whisper",
326
- // ... other config
327
- provider: "cpu",
328
- });
397
+ await asr.load({ type: "whisper", /* ... */ provider: "cpu" });
329
398
  }
330
399
  ```
331
400
 
332
- > **Note:** `isQualcommCpu()` returns `false` on iOS. It checks `/proc/cpuinfo` on Android to detect Qualcomm processors.
401
+ > **Note:** `getQualcommSoc()` returns `""` on iOS.
402
+
403
+ ### Building with QNN Support
404
+
405
+ QNN is **opt-in**. Without `QNN_ROOT`, Android builds do not define `SHERPA_ONNX_ENABLE_QNN`, the default provider is `nnapi`, and no QNN runtime libraries are linked.
406
+
407
+ `QNN_ROOT` is required to enable the QNN execution provider and bundle QNN Binary backend libraries from the Qualcomm AI Runtime (QAIRT) SDK.
408
+
409
+ **Download QAIRT SDK:**
410
+
411
+ Visit [Qualcomm Software Center](https://softwarecenter.qualcomm.com/api/download/software/sdks/Qualcomm_AI_Runtime_Community/All/2.40.0.251030/v2.40.0.251030.zip) to download the SDK (v2.40.0).
412
+
413
+ **Specify QNN_ROOT:**
414
+
415
+ ```bash
416
+ # Via environment variable
417
+ QNN_ROOT=/path/to/qnn/sdk ./gradlew assembleRelease
418
+
419
+ # Or in android/gradle.properties
420
+ QNN_ROOT=/path/to/qnn/sdk
421
+ ```
422
+
423
+ When `QNN_ROOT` is set, the build defines `SHERPA_ONNX_ENABLE_QNN` (so the default provider becomes `qnn`) and links the QNN core library (`QnnHtp`) plus all available HTP version libraries (`QnnHtpV73Stub`/`HtpV73`, `QnnHtpV75Stub`/`HtpV75`, etc.) from the SDK.
424
+
425
+ > **Note:** QNN support is Android-only. On iOS, CoreML is used by default.
333
426
 
334
427
  ## VAD Pre-buffer
335
428
 
@@ -347,27 +440,37 @@ The result is that no speech frames are lost between detection and JS delivery.
347
440
 
348
441
  ## Voice Cloning
349
442
 
443
+ Registered speakers are stored under the platform document directory (`Application Support` on iOS β€” excluded from iCloud backup β€” and `filesDir` on Android) as `<documentDir>/speakers/<id>.bin`. The record holds the embedding and, for `registerSpeakerFromFile`, the reference audio used by zero-shot TTS.
444
+
350
445
  Two voice-cloning paths are exposed:
351
446
 
352
- 1. **Speaker embedding registration** - compute an embedding from reference audio, store it locally, and pass the speaker ID to TTS models that accept a speaker index (e.g. Kokoro multi-speaker, VITS multi-speaker).
353
- 2. **Reference-audio TTS** - models that support prompt-based or zero-shot synthesis (e.g. Pocket) receive the reference embedding directly during synthesis.
447
+ 1. **Speaker embedding registration** β€” compute an embedding from reference audio, store it locally. Embeddings are kept for identity / search; they are not a model speaker index.
448
+ 2. **Reference-audio TTS** β€” models that support prompt-based or zero-shot synthesis (e.g. Pocket) receive stored reference audio during synthesis. Register with `registerSpeakerFromFile` so the reference audio is kept alongside the embedding.
449
+
450
+ `synthesizeWithSpeaker` accepts **two kinds of `speakerId`**:
451
+
452
+ | `speakerId` | Meaning | Typical models |
453
+ |---|---|---|
454
+ | Numeric string, e.g. `"0"` | Model-internal speaker index | Kokoro / VITS multi-speaker |
455
+ | Registered ID, e.g. `"speaker-1"` | Voice-cloning record (needs reference audio) | Pocket (zero-shot) |
354
456
 
355
457
  ```typescript
356
458
  const speaker = speech.createSpeakerManager();
357
459
  await speaker.load({ modelDir: "/path/to/speaker", model: "model.onnx", numThreads: 4 });
358
460
 
359
- const embedding = await speaker.computeEmbedding(referenceAudio);
360
- const registered = await speaker.registerSpeaker("speaker-1", "Alice", embedding);
361
-
362
- // Use the registered speaker with TTS (optional speed override)
461
+ // Zero-shot clone (Pocket): keep the reference audio for synthesis
462
+ const registered = await speaker.registerSpeakerFromFile("speaker-1", "Alice", "/path/to/alice.wav");
363
463
  const cloned = await tts.synthesizeWithSpeaker("Hello, I am Alice.", registered.id, 1.1);
464
+
465
+ // Multi-speaker model index (Kokoro / VITS)
466
+ const voice0 = await tts.synthesizeWithSpeaker("Hello there.", "0");
364
467
  ```
365
468
 
366
469
  ## Threading
367
470
 
368
- Every native inference task runs on a background thread pool:
471
+ Every native inference task runs on a background thread pool (`Promise::async` from react-native-nitro-modules):
369
472
 
370
- - VAD processing
473
+ - VAD processing (plus a dedicated processor thread for streaming segmentation)
371
474
  - Offline / streaming ASR decode
372
475
  - TTS synthesis
373
476
  - Speaker embedding extraction
@@ -392,6 +495,8 @@ The C++ test suite covers:
392
495
 
393
496
  - Audio sample / millisecond conversions
394
497
  - Float vector / byte buffer round-trip
498
+ - Speaker record write/read round-trip (embedding + reference audio)
499
+ - `tryParseSpeakerIndex` edge cases (empty, negative, non-numeric, oversized)
395
500
 
396
501
  ## License
397
502
 
@@ -14,8 +14,8 @@ if(BUILD_ONNX_SPEECH_TESTS)
14
14
  enable_testing()
15
15
  add_executable(NitroOnnxSpeechTests
16
16
  ../cpp/tests/test_core.cpp
17
- ../cpp/ThreadPool.cpp
18
17
  ../cpp/AudioUtils.cpp
18
+ ../cpp/SpeakerRecord.cpp
19
19
  )
20
20
  target_include_directories(NitroOnnxSpeechTests PRIVATE ../cpp)
21
21
  find_package(Threads REQUIRED)
@@ -115,3 +115,56 @@ if(ANDROID)
115
115
  endif()
116
116
  find_package(Threads REQUIRED)
117
117
  target_link_libraries(${PROJECT_NAME} Threads::Threads)
118
+
119
+ # QNN SDK support: pass -DQNN_ROOT=/path/to/qnn/sdk to enable QNN execution provider
120
+ set(QNN_ROOT "" CACHE PATH "Path to Qualcomm QNN SDK root directory")
121
+ if(QNN_ROOT)
122
+ message(NOTICE "πŸ”§ QNN execution provider enabled")
123
+ message(NOTICE "πŸ“ QNN SDK path: ${QNN_ROOT}")
124
+ target_include_directories(${PROJECT_NAME} PRIVATE "${QNN_ROOT}/include")
125
+ target_compile_definitions(${PROJECT_NAME} PRIVATE SHERPA_ONNX_ENABLE_QNN)
126
+
127
+ # QNN libraries are in lib/<arch>/ e.g. lib/aarch64-android/
128
+ set(_QNN_LIB_DIR "${QNN_ROOT}/lib/aarch64-android")
129
+ if(NOT EXISTS "${_QNN_LIB_DIR}")
130
+ # Fallback for older SDK layouts
131
+ set(_QNN_LIB_DIR "${QNN_ROOT}/lib/hexagon-v73")
132
+ endif()
133
+
134
+ # Link QNN core library
135
+ if(EXISTS "${_QNN_LIB_DIR}/libQnnHtp.so")
136
+ add_library(QnnHtp SHARED IMPORTED)
137
+ set_target_properties(QnnHtp PROPERTIES
138
+ IMPORTED_LOCATION "${_QNN_LIB_DIR}/libQnnHtp.so"
139
+ )
140
+ target_link_libraries(${PROJECT_NAME} QnnHtp)
141
+ endif()
142
+
143
+ # Link all available HTP version stubs and runtimes (v73, v75, v79, v81, etc.)
144
+ file(GLOB _QNN_HEXAGON_DIRS "${QNN_ROOT}/lib/hexagon-v*")
145
+ foreach(_HEXAGON_DIR ${_QNN_HEXAGON_DIRS})
146
+ get_filename_component(_HEXAGON_NAME "${_HEXAGON_DIR}" NAME)
147
+ # Extract version number from "hexagon-v73" -> "73"
148
+ string(REGEX REPLACE ".*hexagon-v([0-9]+).*" "\\1" _HTP_VER "${_HEXAGON_NAME}")
149
+ if(_HTP_VER)
150
+ set(_STUB_LIB "QnnHtpV${_HTP_VER}Stub")
151
+ set(_RUNTIME_LIB "HtpV${_HTP_VER}")
152
+
153
+ foreach(_QNN_LIB ${_STUB_LIB} ${_RUNTIME_LIB})
154
+ set(_LIB_PATH "${_HEXAGON_DIR}/lib${_QNN_LIB}.so")
155
+ if(EXISTS "${_LIB_PATH}")
156
+ add_library(${_QNN_LIB} SHARED IMPORTED)
157
+ set_target_properties(${_QNN_LIB} PROPERTIES
158
+ IMPORTED_LOCATION "${_LIB_PATH}"
159
+ )
160
+ target_link_libraries(${PROJECT_NAME} ${_QNN_LIB})
161
+ endif()
162
+ endforeach()
163
+ endif()
164
+ endforeach()
165
+ else()
166
+ # QNN runtime libraries (libQnnHtp.so etc.) are only linked when QNN_ROOT is
167
+ # set. Do not enable the QNN compile-time path without the SDK, otherwise the
168
+ # default provider ("qnn") would fail or silently fall back at runtime.
169
+ message(NOTICE "πŸ”§ QNN execution provider disabled (set -DQNN_ROOT=... to enable)")
170
+ endif()
@@ -24,6 +24,12 @@ android {
24
24
  arguments "-DANDROID_STL=c++_shared"
25
25
  abiFilters (*reactNativeArchitectures())
26
26
 
27
+ // Pass QNN_ROOT to CMake if provided via gradle.properties or env var
28
+ def qnnRoot = findProperty("QNN_ROOT") ?: System.getenv("QNN_ROOT")
29
+ if (qnnRoot) {
30
+ arguments "-DQNN_ROOT=${qnnRoot}"
31
+ }
32
+
27
33
  buildTypes {
28
34
  debug {
29
35
  cppFlags "-O1 -g"
@@ -1,6 +1,4 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
2
  <uses-permission android:name="android.permission.INTERNET" />
3
3
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
4
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
5
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
6
4
  </manifest>
@@ -17,8 +17,8 @@ Java_com_margelo_nitro_onnx_speech_OnnxSpeechPackage_setResourceDir(JNIEnv* env,
17
17
  }
18
18
 
19
19
  extern "C" JNIEXPORT void JNICALL
20
- Java_com_margelo_nitro_onnx_speech_OnnxSpeechPackage_setCacheDir(JNIEnv* env, jclass, jstring dir) {
20
+ Java_com_margelo_nitro_onnx_speech_OnnxSpeechPackage_setDocumentDir(JNIEnv* env, jclass, jstring dir) {
21
21
  const char* utf = env->GetStringUTFChars(dir, nullptr);
22
- margelo::nitro::onnx::speech::setCacheDir(utf);
22
+ margelo::nitro::onnx::speech::setDocumentDir(utf);
23
23
  env->ReleaseStringUTFChars(dir, utf);
24
24
  }
@@ -12,6 +12,7 @@ import java.io.FileOutputStream
12
12
  class OnnxSpeechPackage : ReactPackage {
13
13
  companion object {
14
14
  private const val TAG = "OnnxSpeechPackage"
15
+ private const val RESOURCE_NAME = "silero_vad.onnx"
15
16
 
16
17
  init {
17
18
  NitroOnnxSpeechOnLoad.initializeNative()
@@ -21,34 +22,42 @@ class OnnxSpeechPackage : ReactPackage {
21
22
  private external fun setResourceDir(dir: String)
22
23
 
23
24
  @JvmStatic
24
- private external fun setCacheDir(dir: String)
25
+ private external fun setDocumentDir(dir: String)
25
26
 
26
27
  /**
27
28
  * Copies bundled model files from APK assets to internal storage so that
28
29
  * the C++ layer can access them via real file paths (fopen-compatible).
30
+ * Re-copies when the app version changes or a file is missing.
29
31
  * Called lazily when createNativeModules is first invoked.
30
32
  */
31
33
  internal fun ensureResources(context: Context) {
32
34
  val filesDir = context.filesDir
33
- val marker = File(filesDir, ".resources_extracted")
34
- if (marker.exists()) {
35
- setResourceDir(filesDir.absolutePath)
36
- setCacheDir(filesDir.absolutePath)
37
- return
38
- }
39
- try {
40
- copyAsset(context, "silero_vad.onnx", filesDir)
41
- marker.createNewFile()
42
- setResourceDir(filesDir.absolutePath)
43
- setCacheDir(filesDir.absolutePath)
35
+
36
+ val version = try {
37
+ context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "0"
44
38
  } catch (e: Exception) {
45
- Log.e(TAG, "Failed to extract bundled resources", e)
39
+ "0"
40
+ }
41
+ val marker = File(filesDir, ".resources_extracted_$version")
42
+ val resourceFile = File(filesDir, RESOURCE_NAME)
43
+ if (!marker.exists() || !resourceFile.exists()) {
44
+ try {
45
+ copyAsset(context, RESOURCE_NAME, filesDir, overwrite = true)
46
+ filesDir.listFiles()
47
+ ?.filter { it.name.startsWith(".resources_extracted") }
48
+ ?.forEach { it.delete() }
49
+ marker.createNewFile()
50
+ } catch (e: Exception) {
51
+ Log.e(TAG, "Failed to extract bundled resources", e)
52
+ }
46
53
  }
54
+ setResourceDir(filesDir.absolutePath)
55
+ setDocumentDir(context.filesDir.absolutePath)
47
56
  }
48
57
 
49
- private fun copyAsset(context: Context, name: String, destDir: File) {
58
+ private fun copyAsset(context: Context, name: String, destDir: File, overwrite: Boolean = false) {
50
59
  val dest = File(destDir, name)
51
- if (dest.exists()) return
60
+ if (dest.exists() && !overwrite) return
52
61
  context.assets.open(name).use { input ->
53
62
  FileOutputStream(dest).use { output ->
54
63
  input.copyTo(output)
@@ -57,9 +66,9 @@ class OnnxSpeechPackage : ReactPackage {
57
66
  }
58
67
  }
59
68
 
60
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
69
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
61
70
  ensureResources(reactContext)
62
- return emptyList()
71
+ return super.getModule(name, reactContext)
63
72
  }
64
73
 
65
74
  @Suppress("OVERRIDE_DEPRECATION")