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/NitroOnnxSpeech.podspec
CHANGED
|
@@ -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:**
|
|
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
|
-
- [
|
|
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() /
|
|
52
|
+
β getOnnxSpeech() β createVad() / createTts() / β
|
|
53
|
+
β createOfflineAsr() / createStreamingAsr() β
|
|
38
54
|
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
|
|
39
55
|
β react-native-nitro-modules (zero-copy ArrayBuffer)
|
|
40
56
|
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββ
|
|
41
57
|
β C++ β
|
|
42
|
-
β
|
|
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
|
|
54
|
-
- **Background inference:** Every heavy operation runs on a
|
|
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 |
|
|
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
|
|
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 |
|
|
84
|
-
| ZipVoice | `zipvoice` | internal |
|
|
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 /
|
|
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
|
-
|
|
173
|
-
|
|
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
|
|
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
|
-
##
|
|
364
|
+
## Execution Providers
|
|
365
|
+
|
|
366
|
+
By default, the module selects a platform-appropriate execution provider:
|
|
308
367
|
|
|
309
|
-
|
|
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
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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:** `
|
|
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**
|
|
353
|
-
2. **Reference-audio TTS**
|
|
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
|
-
|
|
360
|
-
const registered = await speaker.
|
|
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
|
|
package/android/CMakeLists.txt
CHANGED
|
@@ -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()
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
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::
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
69
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
61
70
|
ensureResources(reactContext)
|
|
62
|
-
return
|
|
71
|
+
return super.getModule(name, reactContext)
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
@Suppress("OVERRIDE_DEPRECATION")
|