@mentra/bluetooth-sdk 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -18
- package/android/build.gradle +289 -16
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/build.gradle +95 -6
- package/android/settings.gradle +3 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +42 -0
- package/android/silero/proguard-rules.pro +24 -0
- package/android/silero/src/androidTest/assets/hello.wav +0 -0
- package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
- package/android/silero/src/main/assets/LICENSE +21 -0
- package/android/silero/src/main/assets/silero_vad.onnx +0 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
- package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +6 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +165 -27
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +89 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +189 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +22 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +787 -85
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +123 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +12 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +8 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +33 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +101 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +399 -74
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +100 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +569 -220
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +271 -66
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +176 -104
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +12753 -27036
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +54 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +16 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +60 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +12 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +87 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +13 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +180 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +488 -50
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +18 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +64 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +48 -39
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +30 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +98 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/build/BluetoothSdk.types.d.ts +241 -43
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +11 -10
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +36 -23
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +30 -52
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts +4 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +25 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +23 -0
- package/build/index.js.map +1 -1
- package/build/react/useMentraBluetooth.d.ts +2 -2
- package/build/react/useMentraBluetooth.d.ts.map +1 -1
- package/build/react/useMentraBluetooth.js +3 -3
- package/build/react/useMentraBluetooth.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +174 -70
- package/ios/LocalPhotoUploadServer.swift +370 -105
- package/ios/MentraBluetoothSDK.podspec +3 -1
- package/ios/MentraPhotoReceiverModule.swift +62 -10
- package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +668 -26
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +13 -9
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Source/BluetoothSdkDefaults.swift +6 -0
- package/ios/Source/Bridge.swift +58 -19
- package/ios/Source/DeviceManager.swift +149 -17
- package/ios/Source/DeviceStore.swift +16 -6
- package/ios/Source/MentraBluetoothSDK.swift +803 -47
- package/ios/Source/ObservableStore.swift +6 -0
- package/ios/Source/{Audio → audio}/AudioModels.swift +9 -7
- package/ios/Source/{Camera → camera}/CameraModels.swift +225 -16
- package/ios/Source/controllers/ControllerManager.swift +4 -3
- package/ios/Source/controllers/R1.swift +3 -3
- package/ios/Source/{Events → events}/BluetoothEvents.swift +183 -1
- package/ios/Source/{Internal → internal}/ValueParsing.swift +10 -1
- package/ios/Source/{Requests → requests}/DisplayRequests.swift +28 -4
- package/ios/Source/services/PhoneMic.swift +1 -0
- package/ios/Source/sgcs/G1.swift +2 -2
- package/ios/Source/sgcs/G2.swift +645 -403
- package/ios/Source/sgcs/Mach1.swift +2 -2
- package/ios/Source/sgcs/MentraLive.swift +812 -319
- package/ios/Source/sgcs/MentraNex.swift +412 -182
- package/ios/Source/sgcs/SGCManager.swift +57 -3
- package/ios/Source/sgcs/Simulated.swift +7 -3
- package/ios/Source/sgcs/mentraos_ble.pb.swift +3073 -3134
- package/ios/Source/status/DeviceStatus.swift +974 -0
- package/ios/Source/{Status → status}/WifiHotspotStatus.swift +4 -4
- package/ios/Source/{Streaming → streaming}/StreamModels.swift +24 -9
- package/ios/Source/stt/STTTools.swift +27 -12
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +31 -8
- package/ios/Source/tts/TTSTools.swift +164 -0
- package/ios/Source/utils/JSCExperiment.swift +7 -7
- package/ios/Source/utils/MessageChunkReassembler.swift +20 -1
- package/ios/Source/utils/MessageChunker.swift +78 -21
- package/ios/Source/utils/TarBz2Extractor.swift +53 -2
- package/package.json +1 -1
- package/plugin/build/withAndroid.js +68 -4
- package/src/BluetoothSdk.types.ts +331 -59
- package/src/_private/BluetoothSdkModule.ts +113 -102
- package/src/_private/photoRequestPayload.ts +28 -0
- package/src/index.ts +58 -15
- package/src/react/useMentraBluetooth.ts +6 -4
- package/android/.project +0 -28
- package/android/lc3Lib/.project +0 -28
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +0 -162
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +0 -1852
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +0 -674
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
- package/ios/Source/Status/DeviceStatus.swift +0 -471
- /package/ios/Source/{Connection → connection}/ScanSession.swift +0 -0
- /package/ios/Source/{Errors → errors}/BluetoothError.swift +0 -0
- /package/ios/Source/{Internal → internal}/BluetoothAvailability.swift +0 -0
- /package/ios/Source/{Status → status}/RuntimeState.swift +0 -0
- /package/ios/Source/{Types → types}/DeviceModels.swift +0 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
package com.konovalov.vad.silero
|
|
2
|
+
|
|
3
|
+
import ai.onnxruntime.OnnxTensor
|
|
4
|
+
import ai.onnxruntime.OrtEnvironment
|
|
5
|
+
import ai.onnxruntime.OrtException
|
|
6
|
+
import ai.onnxruntime.OrtSession
|
|
7
|
+
import ai.onnxruntime.OrtSession.SessionOptions
|
|
8
|
+
import android.content.Context
|
|
9
|
+
import com.konovalov.vad.silero.config.FrameSize
|
|
10
|
+
import com.konovalov.vad.silero.config.Mode
|
|
11
|
+
import com.konovalov.vad.silero.config.SampleRate
|
|
12
|
+
import com.konovalov.vad.silero.utils.AudioUtils.getFramesCount
|
|
13
|
+
import com.konovalov.vad.silero.utils.AudioUtils.toFloatArray
|
|
14
|
+
import com.konovalov.vad.silero.utils.TensorMap
|
|
15
|
+
import java.io.Closeable
|
|
16
|
+
import java.nio.FloatBuffer
|
|
17
|
+
import java.nio.LongBuffer
|
|
18
|
+
import kotlin.reflect.safeCast
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Created by Georgiy Konovalov on 6/1/2023.
|
|
22
|
+
*
|
|
23
|
+
* The Silero VAD algorithm, based on DNN, analyzes the audio signal to determine whether it
|
|
24
|
+
* contains speech or non-speech segments. It offers higher accuracy in differentiating speech from
|
|
25
|
+
* background noise compared to the WebRTC VAD algorithm.
|
|
26
|
+
*
|
|
27
|
+
* The Silero VAD supports the following parameters:
|
|
28
|
+
*
|
|
29
|
+
* Sample Rates:
|
|
30
|
+
*
|
|
31
|
+
* 8000Hz,
|
|
32
|
+
* 16000Hz
|
|
33
|
+
*
|
|
34
|
+
* Frame Sizes (per sample rate):
|
|
35
|
+
*
|
|
36
|
+
* For 8000Hz: 80, 160, 240
|
|
37
|
+
* For 16000Hz: 160, 320, 480
|
|
38
|
+
*
|
|
39
|
+
* Mode:
|
|
40
|
+
*
|
|
41
|
+
* NORMAL,
|
|
42
|
+
* LOW_BITRATE,
|
|
43
|
+
* AGGRESSIVE,
|
|
44
|
+
* VERY_AGGRESSIVE
|
|
45
|
+
*
|
|
46
|
+
* Please note that the VAD class supports these specific combinations of sample
|
|
47
|
+
* rates and frame sizes, and the classifiers determine the aggressiveness of the voice
|
|
48
|
+
* activity detection algorithm.
|
|
49
|
+
*
|
|
50
|
+
* @param context is required for reading the model file from file system.
|
|
51
|
+
* @param sampleRate is required for processing audio input.
|
|
52
|
+
* @param frameSize is required for processing audio input.
|
|
53
|
+
* @param mode is required for the VAD model.
|
|
54
|
+
* @param speechDurationMs is minimum duration in milliseconds for speech segments (optional).
|
|
55
|
+
* @param silenceDurationMs is minimum duration in milliseconds for silence segments (optional).
|
|
56
|
+
*/
|
|
57
|
+
class VadSilero(
|
|
58
|
+
context: Context,
|
|
59
|
+
sampleRate: SampleRate,
|
|
60
|
+
frameSize: FrameSize,
|
|
61
|
+
mode: Mode,
|
|
62
|
+
speechDurationMs: Int = 0,
|
|
63
|
+
silenceDurationMs: Int = 0
|
|
64
|
+
) : Closeable {
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Valid Sample Rates and Frame Sizes for Silero VAD DNN model.
|
|
68
|
+
*/
|
|
69
|
+
var supportedParameters: Map<SampleRate, Set<FrameSize>> = mapOf(
|
|
70
|
+
SampleRate.SAMPLE_RATE_8K to setOf(
|
|
71
|
+
FrameSize.FRAME_SIZE_256,
|
|
72
|
+
FrameSize.FRAME_SIZE_512,
|
|
73
|
+
FrameSize.FRAME_SIZE_768
|
|
74
|
+
),
|
|
75
|
+
SampleRate.SAMPLE_RATE_16K to setOf(
|
|
76
|
+
FrameSize.FRAME_SIZE_512,
|
|
77
|
+
FrameSize.FRAME_SIZE_1024,
|
|
78
|
+
FrameSize.FRAME_SIZE_1536
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
private set
|
|
82
|
+
|
|
83
|
+
private val env: OrtEnvironment
|
|
84
|
+
private val session: OrtSession
|
|
85
|
+
private var isInitiated: Boolean = false
|
|
86
|
+
|
|
87
|
+
private var h = FloatArray(128)
|
|
88
|
+
private var c = FloatArray(128)
|
|
89
|
+
|
|
90
|
+
private var speechFramesCount = 0
|
|
91
|
+
private var silenceFramesCount = 0
|
|
92
|
+
private var maxSpeechFramesCount = 0
|
|
93
|
+
private var maxSilenceFramesCount = 0
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Determines if the provided audio data contains speech based on the inference result.
|
|
97
|
+
* The audio data is passed to the model for prediction. The result is obtained and compared
|
|
98
|
+
* with the threshold value to determine if it represents speech.
|
|
99
|
+
*
|
|
100
|
+
* @param audioData audio data to analyze.
|
|
101
|
+
* @return 'true' if speech is detected, 'false' otherwise.
|
|
102
|
+
*/
|
|
103
|
+
fun isSpeech(audioData: ShortArray): Boolean {
|
|
104
|
+
return isContinuousSpeech(predict(toFloatArray(audioData)))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Determines if the provided audio data contains speech based on the inference result.
|
|
109
|
+
* The audio data is passed to the model for prediction. The result is obtained and compared
|
|
110
|
+
* with the threshold value to determine if it represents speech.
|
|
111
|
+
* Size of audio chunk for ByteArray should be 2x of Frame size.
|
|
112
|
+
*
|
|
113
|
+
* @param audioData audio data to analyze.
|
|
114
|
+
* @return 'true' if speech is detected, 'false' otherwise.
|
|
115
|
+
*/
|
|
116
|
+
fun isSpeech(audioData: ByteArray): Boolean {
|
|
117
|
+
return isContinuousSpeech(predict(toFloatArray(audioData)))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Determines if the provided audio data contains speech based on the inference result.
|
|
122
|
+
* The audio data is passed to the model for prediction. The result is obtained and compared
|
|
123
|
+
* with the threshold value to determine if it represents speech.
|
|
124
|
+
*
|
|
125
|
+
* @param audioData audio data to analyze.
|
|
126
|
+
* @return 'true' if speech is detected, 'false' otherwise.
|
|
127
|
+
*/
|
|
128
|
+
fun isSpeech(audioData: FloatArray): Boolean {
|
|
129
|
+
return isContinuousSpeech(predict(audioData))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* This method designed to detect long utterances without returning false
|
|
134
|
+
* positive results when user makes pauses between sentences.
|
|
135
|
+
*
|
|
136
|
+
* @param isSpeech predicted frame result.
|
|
137
|
+
* @return 'true' if speech is detected, 'false' otherwise.
|
|
138
|
+
*/
|
|
139
|
+
private fun isContinuousSpeech(isSpeech: Boolean): Boolean {
|
|
140
|
+
if (isSpeech) {
|
|
141
|
+
if (speechFramesCount <= maxSpeechFramesCount) speechFramesCount++
|
|
142
|
+
|
|
143
|
+
if (speechFramesCount > maxSpeechFramesCount) {
|
|
144
|
+
silenceFramesCount = 0
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
if (silenceFramesCount <= maxSilenceFramesCount) silenceFramesCount++
|
|
149
|
+
|
|
150
|
+
if (silenceFramesCount > maxSilenceFramesCount) {
|
|
151
|
+
speechFramesCount = 0
|
|
152
|
+
return false
|
|
153
|
+
} else if (speechFramesCount > maxSpeechFramesCount) {
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Determines if the provided audio data contains speech based on the inference result.
|
|
162
|
+
* The audio data is passed to the model for prediction. The result is extracted and compared
|
|
163
|
+
* with the threshold value to determine if it represents speech.
|
|
164
|
+
*
|
|
165
|
+
* @param audioData audio data to analyze.
|
|
166
|
+
* @return 'true' if speech is detected, 'false' otherwise.
|
|
167
|
+
*/
|
|
168
|
+
private fun predict(audioData: FloatArray): Boolean {
|
|
169
|
+
checkState()
|
|
170
|
+
|
|
171
|
+
return createInputTensors(audioData).use { tensors ->
|
|
172
|
+
session.run(tensors).use { result ->
|
|
173
|
+
extractResult(result) > threshold()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Retrieves and processes the output tensors to obtain the confidence value.
|
|
180
|
+
* The output tensor contains the confidence value, as well as the updated hidden state (H)
|
|
181
|
+
* and cell state (C) values. The H and C values are flattened and converted to float arrays
|
|
182
|
+
* for further processing.
|
|
183
|
+
*
|
|
184
|
+
* @param result output result of the inference session.
|
|
185
|
+
* @return confidence value.
|
|
186
|
+
*/
|
|
187
|
+
private fun extractResult(result: OrtSession.Result): Float {
|
|
188
|
+
val confidence: Array<FloatArray>? = unpack(result, OutputTensors.OUTPUT)
|
|
189
|
+
|
|
190
|
+
flattenArray(unpack(result, OutputTensors.CN))?.let { c = it }
|
|
191
|
+
flattenArray(unpack(result, OutputTensors.HN))?.let { h = it }
|
|
192
|
+
|
|
193
|
+
return confidence?.getOrNull(0)?.getOrNull(0) ?: 0f
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Unpacks the value of the specified tensor from an OrtSession.Result object
|
|
198
|
+
* and attempts to cast it to an array of the specified generic type {@code T}.
|
|
199
|
+
*
|
|
200
|
+
* @param output OrtSession.Result object from which to retrieve the value.
|
|
201
|
+
* @param index specifying the position from which to retrieve the value.
|
|
202
|
+
* @param <T> generic type to which the value should be cast.
|
|
203
|
+
* @return array of type {@code T} if the casting is successful,
|
|
204
|
+
* or {@code null} if an exception occurs
|
|
205
|
+
* or if the value cannot be cast to the specified type.
|
|
206
|
+
*/
|
|
207
|
+
private inline fun <reified T> unpack(output: OrtSession.Result, index: Int): Array<T>? {
|
|
208
|
+
return try {
|
|
209
|
+
Array<T>::class.safeCast(output.get(index).value)
|
|
210
|
+
} catch (e: OrtException) {
|
|
211
|
+
null
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Flattens a multi-dimensional array of FloatArrays into a one-dimensional FloatArray.
|
|
217
|
+
*
|
|
218
|
+
* @param array multi-dimensional array to be flattened.
|
|
219
|
+
* @return flattened one-dimensional FloatArray if the input array is not null,
|
|
220
|
+
* or {@code null} if the input array is null.
|
|
221
|
+
*/
|
|
222
|
+
private fun flattenArray(array: Array<Array<FloatArray>>?): FloatArray? {
|
|
223
|
+
return array?.flatten()?.flatMap { it.asIterable() }?.toFloatArray()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Creates and returns a map of input tensors for the given audio data, Sample Rate and Frame Size.
|
|
228
|
+
* The audio data is converted to a float array and wrapped in an OnnxTensor with the
|
|
229
|
+
* corresponding tensor shape. The sample rate, hidden state (H), and cell state (C) tensors
|
|
230
|
+
* are also created and added to the map.
|
|
231
|
+
*
|
|
232
|
+
* @param audioData audio data to analyze.
|
|
233
|
+
* @throws OrtException if there was an error in creating the tensors or getting the OrtEnvironment.
|
|
234
|
+
* @return map of input tensors as a TensorMap<String, OnnxTensor>.
|
|
235
|
+
*/
|
|
236
|
+
private fun createInputTensors(audioData: FloatArray): TensorMap<String, OnnxTensor> {
|
|
237
|
+
return TensorMap<String, OnnxTensor>().apply {
|
|
238
|
+
InputTensors.INPUT to OnnxTensor.createTensor(
|
|
239
|
+
env,
|
|
240
|
+
FloatBuffer.wrap(audioData),
|
|
241
|
+
longArrayOf(1, frameSize.value.toLong())
|
|
242
|
+
)
|
|
243
|
+
InputTensors.SR to OnnxTensor.createTensor(
|
|
244
|
+
env,
|
|
245
|
+
LongBuffer.wrap(longArrayOf(sampleRate.value.toLong())),
|
|
246
|
+
longArrayOf(1)
|
|
247
|
+
)
|
|
248
|
+
InputTensors.H to OnnxTensor.createTensor(
|
|
249
|
+
env,
|
|
250
|
+
FloatBuffer.wrap(h),
|
|
251
|
+
longArrayOf(2, 1, 64)
|
|
252
|
+
)
|
|
253
|
+
InputTensors.C to OnnxTensor.createTensor(
|
|
254
|
+
env,
|
|
255
|
+
FloatBuffer.wrap(c),
|
|
256
|
+
longArrayOf(2, 1, 64)
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Retrieves the model data as a byte array from silero_vad.onnx.
|
|
263
|
+
*
|
|
264
|
+
* @param context android context.
|
|
265
|
+
* @return model data as a ByteArray.
|
|
266
|
+
*/
|
|
267
|
+
private fun getModel(context: Context): ByteArray {
|
|
268
|
+
return context.assets.open("silero_vad.onnx").use { it.readBytes() }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Calculates and returns the threshold value based on the value of detection mode.
|
|
273
|
+
* The threshold value represents the confidence level required for VAD to make proper decision.
|
|
274
|
+
* ex. Mode.VERY_AGGRESSIVE requiring a very high prediction accuracy from the model.
|
|
275
|
+
*
|
|
276
|
+
* @return threshold Float value.
|
|
277
|
+
*/
|
|
278
|
+
private fun threshold(): Float = when (mode) {
|
|
279
|
+
Mode.NORMAL -> 0.5f
|
|
280
|
+
Mode.AGGRESSIVE -> 0.8f
|
|
281
|
+
Mode.VERY_AGGRESSIVE -> 0.95f
|
|
282
|
+
else -> 0f
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Set, retrieve and validate sample rate for Vad Model.
|
|
287
|
+
*
|
|
288
|
+
* Valid Sample Rates:
|
|
289
|
+
*
|
|
290
|
+
* 8000Hz,
|
|
291
|
+
* 16000Hz
|
|
292
|
+
*
|
|
293
|
+
* @param sampleRate is required for processing audio input.
|
|
294
|
+
* @throws IllegalArgumentException if there was invalid sample rate.
|
|
295
|
+
*/
|
|
296
|
+
var sampleRate: SampleRate = sampleRate
|
|
297
|
+
set(sampleRate) {
|
|
298
|
+
require(supportedParameters.containsKey(sampleRate)) {
|
|
299
|
+
"VAD doesn't support Sample Rate:${sampleRate}!"
|
|
300
|
+
}
|
|
301
|
+
field = sampleRate
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Set, retrieve and validate frame size for Vad Model.
|
|
306
|
+
*
|
|
307
|
+
* Valid Frame Sizes (per sample rate):
|
|
308
|
+
*
|
|
309
|
+
* For 8000Hz: 256, 512, 768
|
|
310
|
+
* For 16000Hz: 512, 1024, 1536
|
|
311
|
+
*
|
|
312
|
+
* @param frameSize is required for processing audio input.
|
|
313
|
+
* @throws IllegalArgumentException if there was invalid frame size.
|
|
314
|
+
*/
|
|
315
|
+
var frameSize: FrameSize = frameSize
|
|
316
|
+
set(frameSize) {
|
|
317
|
+
require(supportedParameters[sampleRate]?.contains(frameSize) ?: false) {
|
|
318
|
+
"VAD doesn't support Sample rate:${sampleRate} and Frame Size:${frameSize}!"
|
|
319
|
+
}
|
|
320
|
+
field = frameSize
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Set and retrieve detection mode for Vad model.
|
|
325
|
+
*
|
|
326
|
+
* Mode:
|
|
327
|
+
*
|
|
328
|
+
* NORMAL,
|
|
329
|
+
* LOW_BITRATE,
|
|
330
|
+
* AGGRESSIVE,
|
|
331
|
+
* VERY_AGGRESSIVE
|
|
332
|
+
*
|
|
333
|
+
* @param mode is required for the VAD model.
|
|
334
|
+
*/
|
|
335
|
+
var mode: Mode = mode
|
|
336
|
+
set(mode) {
|
|
337
|
+
field = mode
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Set, retrieve and validate speechDurationMs for Vad Model.
|
|
342
|
+
* The value of this parameter will define the necessary and sufficient duration of positive
|
|
343
|
+
* results to recognize result as speech. This parameter is optional.
|
|
344
|
+
*
|
|
345
|
+
* Permitted range (0ms >= speechDurationMs <= 300000ms).
|
|
346
|
+
*
|
|
347
|
+
* Parameters used for {@link VadSilero.isSpeech}.
|
|
348
|
+
*
|
|
349
|
+
* @param speechDurationMs speech duration ms.
|
|
350
|
+
* @throws IllegalArgumentException if out of permitted range.
|
|
351
|
+
*/
|
|
352
|
+
var speechDurationMs: Int = speechDurationMs
|
|
353
|
+
set(speechDurationMs) {
|
|
354
|
+
require(speechDurationMs in 0..300000) {
|
|
355
|
+
"The parameter 'speechDurationMs' should 0ms >= speechDurationMs <= 300000ms!"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
field = speechDurationMs
|
|
359
|
+
maxSpeechFramesCount = getFramesCount(sampleRate.value, frameSize.value, speechDurationMs)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Set, retrieve and validate silenceDurationMs for Vad Model.
|
|
364
|
+
* The value of this parameter will define the necessary and sufficient duration of
|
|
365
|
+
* negative results to recognize it as silence. This parameter is optional.
|
|
366
|
+
*
|
|
367
|
+
* Permitted range (0ms >= silenceDurationMs <= 300000ms).
|
|
368
|
+
*
|
|
369
|
+
* Parameters used in {@link VadSilero.isSpeech}.
|
|
370
|
+
*
|
|
371
|
+
* @param silenceDurationMs silence duration ms.
|
|
372
|
+
* @throws IllegalArgumentException if out of permitted range.
|
|
373
|
+
*/
|
|
374
|
+
var silenceDurationMs: Int = silenceDurationMs
|
|
375
|
+
set(silenceDurationMs) {
|
|
376
|
+
require(silenceDurationMs in 0..300000) {
|
|
377
|
+
"The parameter 'silenceDurationMs' should be 0ms >= silenceDurationMs <= 300000ms!"
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
field = silenceDurationMs
|
|
381
|
+
maxSilenceFramesCount = getFramesCount(sampleRate.value, frameSize.value, silenceDurationMs)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Closes the ONNX Session and releases all associated resources.
|
|
386
|
+
* This method should be called when the VAD is no longer needed to free up system resources.
|
|
387
|
+
*/
|
|
388
|
+
override fun close() {
|
|
389
|
+
checkState()
|
|
390
|
+
isInitiated = false
|
|
391
|
+
|
|
392
|
+
session.close()
|
|
393
|
+
env.close()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Check if VAD session already closed.
|
|
398
|
+
*
|
|
399
|
+
* @throws IllegalArgumentException if session already closed.
|
|
400
|
+
*/
|
|
401
|
+
private fun checkState() {
|
|
402
|
+
require(isInitiated) { "You can't use Vad after closing session!" }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Constants representing the input tensor names used during model prediction.
|
|
407
|
+
*/
|
|
408
|
+
private object InputTensors {
|
|
409
|
+
const val INPUT = "input"
|
|
410
|
+
const val SR = "sr"
|
|
411
|
+
const val H = "h"
|
|
412
|
+
const val C = "c"
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Constants representing the output tensor names used when the model returns a result.
|
|
417
|
+
*/
|
|
418
|
+
private object OutputTensors {
|
|
419
|
+
const val OUTPUT = 0
|
|
420
|
+
const val HN = 1
|
|
421
|
+
const val CN = 2
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Initializes the ONNIX Runtime by creating a session with the provided
|
|
426
|
+
* model file and session options.
|
|
427
|
+
*
|
|
428
|
+
* @param context is required for accessing the model file.
|
|
429
|
+
* @throws IllegalArgumentException if invalid parameters have been set for the model.
|
|
430
|
+
* @throws OrtException if the model failed to parse, wasn't compatible or caused an error.
|
|
431
|
+
*/
|
|
432
|
+
init {
|
|
433
|
+
this.sampleRate = sampleRate
|
|
434
|
+
this.frameSize = frameSize
|
|
435
|
+
this.mode = mode
|
|
436
|
+
this.silenceDurationMs = silenceDurationMs
|
|
437
|
+
this.speechDurationMs = speechDurationMs
|
|
438
|
+
|
|
439
|
+
val sessionOptions = SessionOptions().apply {
|
|
440
|
+
setIntraOpNumThreads(1)
|
|
441
|
+
setInterOpNumThreads(1)
|
|
442
|
+
setOptimizationLevel(SessionOptions.OptLevel.ALL_OPT)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
this.env = OrtEnvironment.getEnvironment()
|
|
446
|
+
this.session = env.createSession(getModel(context), sessionOptions)
|
|
447
|
+
this.isInitiated = true
|
|
448
|
+
}
|
|
449
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.konovalov.vad.silero.config
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Created by Georgiy Konovalov on 6/1/2023.
|
|
5
|
+
*
|
|
6
|
+
* Enum class representing different Frame Sizes used in the VAD algorithm.
|
|
7
|
+
* @property value numeric value associated with the FrameSize.
|
|
8
|
+
*/
|
|
9
|
+
enum class FrameSize(val value: Int) {
|
|
10
|
+
FRAME_SIZE_256(256),
|
|
11
|
+
FRAME_SIZE_512(512),
|
|
12
|
+
FRAME_SIZE_768(768),
|
|
13
|
+
FRAME_SIZE_1024(1024),
|
|
14
|
+
FRAME_SIZE_1536(1536);
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.konovalov.vad.silero.config
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Created by Georgiy Konovalov on 6/1/2023.
|
|
5
|
+
*
|
|
6
|
+
* Enum class representing different Modes used in the VAD algorithm.
|
|
7
|
+
* @property value numeric value associated with the Mode.
|
|
8
|
+
*/
|
|
9
|
+
enum class Mode(val value: Int) {
|
|
10
|
+
OFF(0),
|
|
11
|
+
NORMAL(1),
|
|
12
|
+
AGGRESSIVE(2),
|
|
13
|
+
VERY_AGGRESSIVE(3);
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package com.konovalov.vad.silero.config
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Created by Georgiy Konovalov on 6/2/2023.
|
|
5
|
+
*
|
|
6
|
+
* Enum class representing different Sample Rates used in the VAD algorithm.
|
|
7
|
+
* @property value numeric value associated with the SampleRate.
|
|
8
|
+
*/
|
|
9
|
+
enum class SampleRate(val value:Int) {
|
|
10
|
+
SAMPLE_RATE_8K(8000),
|
|
11
|
+
SAMPLE_RATE_16K(16000);
|
|
12
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package com.konovalov.vad.silero.utils
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Created by Georgiy Konovalov on 7/18/2023.
|
|
5
|
+
*
|
|
6
|
+
* AudioUtils is a utility class that provides various functions to work with audio data
|
|
7
|
+
* in different formats. It contains static methods that facilitate audio data conversions,
|
|
8
|
+
* processing, and manipulation. This class aims to simplify common audio-related tasks
|
|
9
|
+
* and ensure reusability of audio processing logic across the application.
|
|
10
|
+
*/
|
|
11
|
+
object AudioUtils {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert audio data from ByteArray to FloatArray.
|
|
15
|
+
*
|
|
16
|
+
* @param audio is audio data for conversion.
|
|
17
|
+
* @return converted audio data as FloatArray.
|
|
18
|
+
*/
|
|
19
|
+
fun toFloatArray(audio: ByteArray): FloatArray {
|
|
20
|
+
return FloatArray(audio.size / 2) { i ->
|
|
21
|
+
((audio[2 * i].toInt() and 0xFF) or (audio[2 * i + 1].toInt() shl 8)) / 32767.0f
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert audio data from ShortArray to FloatArray.
|
|
27
|
+
*
|
|
28
|
+
* @param audio is audio data for conversion.
|
|
29
|
+
* @return converted audio data as FloatArray.
|
|
30
|
+
*/
|
|
31
|
+
fun toFloatArray(audio: ShortArray): FloatArray {
|
|
32
|
+
return FloatArray(audio.size) { i ->
|
|
33
|
+
audio[i] / 32767.0f
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Calculates the frame count based on the duration in milliseconds,
|
|
39
|
+
* frequency and frame size.
|
|
40
|
+
*
|
|
41
|
+
* @param durationMs duration in milliseconds.
|
|
42
|
+
* @return frame count.
|
|
43
|
+
*/
|
|
44
|
+
fun getFramesCount(sampleRate: Int, frameSize: Int, durationMs: Int): Int {
|
|
45
|
+
return durationMs / (frameSize / (sampleRate / 1000))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package com.konovalov.vad.silero.utils
|
|
2
|
+
|
|
3
|
+
import ai.onnxruntime.OnnxTensorLike
|
|
4
|
+
import java.io.Closeable
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Created by Georgiy Konovalov on 1/25/2025.
|
|
8
|
+
*
|
|
9
|
+
* TensorMap is a utility class designed to manage a collection of tensors while
|
|
10
|
+
* ensuring proper resource cleanup. It extends `HashMap` to provide standard
|
|
11
|
+
* map functionality and implements `Closeable` to handle the lifecycle of
|
|
12
|
+
* the tensors within the map.
|
|
13
|
+
*
|
|
14
|
+
* This class is particularly useful in scenarios where tensors need to be
|
|
15
|
+
* systematically organized and safely closed to avoid resource leaks.
|
|
16
|
+
*
|
|
17
|
+
* @param K the type of keys in the map, representing identifiers for the tensors.
|
|
18
|
+
* @param V the type of tensor values in the map, restricted to `OnnxTensorLike` implementations.
|
|
19
|
+
*/
|
|
20
|
+
class TensorMap<K, V : OnnxTensorLike> : LinkedHashMap<K, V>(), Closeable {
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Infix function that allows for a more natural syntax when adding entries to the map.
|
|
24
|
+
* Instead of using put() or map[key] = value, you can write: key to tensor
|
|
25
|
+
*
|
|
26
|
+
* @param tensor The OnnxTensor to associate with the key
|
|
27
|
+
* @return The tensor that was just added
|
|
28
|
+
*/
|
|
29
|
+
infix fun K.to(tensor: V): V {
|
|
30
|
+
this@TensorMap[this] = tensor
|
|
31
|
+
return tensor
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Releases resources associated with all tensors in the map.
|
|
36
|
+
* Iterates through the map's values and calls the `close` method on each tensor.
|
|
37
|
+
*/
|
|
38
|
+
override fun close() {
|
|
39
|
+
values.forEach { it.close() }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package com.konovalov.vad.silero.utils
|
|
2
|
+
|
|
3
|
+
import com.konovalov.vad.silero.utils.AudioUtils.getFramesCount
|
|
4
|
+
import com.konovalov.vad.silero.utils.AudioUtils.toFloatArray
|
|
5
|
+
import org.junit.Assert.assertArrayEquals
|
|
6
|
+
import org.junit.Assert.assertEquals
|
|
7
|
+
import org.junit.Test
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Created by Georgiy Konovalov on 12/04/2023.
|
|
11
|
+
*
|
|
12
|
+
* Unit tests for AudioUtils class.
|
|
13
|
+
*/
|
|
14
|
+
class AudioUtilsTest {
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun `convert ShortArray to FloatArray`() {
|
|
18
|
+
val input = shortArrayOf(100, 200, 300)
|
|
19
|
+
val expected = floatArrayOf(0.003051851f, 0.006103702f, 0.009155553f)
|
|
20
|
+
val result = toFloatArray(input)
|
|
21
|
+
|
|
22
|
+
assertArrayEquals(expected, result, 0f)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun `convert ByteArray to FloatArray`() {
|
|
27
|
+
val input = byteArrayOf(10, 20, 30)
|
|
28
|
+
val expected = floatArrayOf(0.15655996f)
|
|
29
|
+
val result = toFloatArray(input)
|
|
30
|
+
|
|
31
|
+
assertArrayEquals(expected, result, 0f)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Test
|
|
35
|
+
fun `getFramesCount calculates frame count correctly`() {
|
|
36
|
+
val sampleRate = 8000
|
|
37
|
+
val frameSize = 256
|
|
38
|
+
val durationMs = 300
|
|
39
|
+
val expected = 9
|
|
40
|
+
val result = getFramesCount(sampleRate, frameSize, durationMs)
|
|
41
|
+
|
|
42
|
+
assertEquals(expected, result)
|
|
43
|
+
}
|
|
44
|
+
}
|