@mentra/bluetooth-sdk 0.1.0
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 +278 -0
- package/android/build.gradle +167 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +109 -0
- package/android/lc3Lib/proguard-rules.pro +33 -0
- package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
- package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
- package/android/lc3Lib/src/main/java/com/mentra/lc3Lib/Lc3Cpp.java +38 -0
- package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
- package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
- package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
- package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
- package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
- package/android/settings.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +40 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +528 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +610 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +1616 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +314 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +1710 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +554 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ObservableStore.kt +68 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +934 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +175 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +1109 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +3974 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3551 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1327 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +6959 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1655 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +53675 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +202 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +224 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +340 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +465 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +221 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +179 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +44 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +76 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +791 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +186 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +589 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +303 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/app.plugin.js +1 -0
- package/build/BluetoothSdk.types.d.ts +498 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +23 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/BluetoothSdkModule.d.ts +71 -0
- package/build/BluetoothSdkModule.d.ts.map +1 -0
- package/build/BluetoothSdkModule.js +106 -0
- package/build/BluetoothSdkModule.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/BluetoothSdkModule.swift +665 -0
- package/ios/MentraBluetoothSDK.podspec +77 -0
- package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
- package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
- package/ios/Packages/CoreObjC/attdet.c +92 -0
- package/ios/Packages/CoreObjC/attdet.h +44 -0
- package/ios/Packages/CoreObjC/bits.c +375 -0
- package/ios/Packages/CoreObjC/bits.h +315 -0
- package/ios/Packages/CoreObjC/bwdet.c +129 -0
- package/ios/Packages/CoreObjC/bwdet.h +69 -0
- package/ios/Packages/CoreObjC/common.h +151 -0
- package/ios/Packages/CoreObjC/energy.c +70 -0
- package/ios/Packages/CoreObjC/energy.h +43 -0
- package/ios/Packages/CoreObjC/fastmath.h +158 -0
- package/ios/Packages/CoreObjC/lc3.c +704 -0
- package/ios/Packages/CoreObjC/lc3.h +313 -0
- package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
- package/ios/Packages/CoreObjC/lc3_private.h +163 -0
- package/ios/Packages/CoreObjC/ltpf.c +905 -0
- package/ios/Packages/CoreObjC/ltpf.h +111 -0
- package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
- package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
- package/ios/Packages/CoreObjC/makefile.mk +35 -0
- package/ios/Packages/CoreObjC/mdct.c +469 -0
- package/ios/Packages/CoreObjC/mdct.h +57 -0
- package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
- package/ios/Packages/CoreObjC/meson.build +61 -0
- package/ios/Packages/CoreObjC/plc.c +61 -0
- package/ios/Packages/CoreObjC/plc.h +57 -0
- package/ios/Packages/CoreObjC/rnnoise.h +114 -0
- package/ios/Packages/CoreObjC/sns.c +880 -0
- package/ios/Packages/CoreObjC/sns.h +103 -0
- package/ios/Packages/CoreObjC/spec.c +907 -0
- package/ios/Packages/CoreObjC/spec.h +119 -0
- package/ios/Packages/CoreObjC/tables.c +3457 -0
- package/ios/Packages/CoreObjC/tables.h +94 -0
- package/ios/Packages/CoreObjC/tns.c +457 -0
- package/ios/Packages/CoreObjC/tns.h +99 -0
- package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
- package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +1659 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +162 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +1852 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +674 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +44 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- 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/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
- package/ios/Packages/VAD/Common/VAD/VADQuality.swift +41 -0
- package/ios/Packages/VAD/Common/VAD/VADState.swift +26 -0
- package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +29 -0
- package/ios/Packages/VAD/Common/VAD/VADType.swift +14 -0
- package/ios/Packages/VAD/Data/Configuration.swift +68 -0
- package/ios/Packages/VAD/Data/FrameSize.swift +39 -0
- package/ios/Packages/VAD/Data/Record.swift +13 -0
- package/ios/Packages/VAD/Data/Result.swift +22 -0
- package/ios/Packages/VAD/Data/SampleRate.swift +48 -0
- package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
- package/ios/Packages/VAD/Silero/SileroVAD.swift +284 -0
- package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +64 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/Source/Bridge.swift +353 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +1478 -0
- package/ios/Source/DeviceStore.swift +295 -0
- package/ios/Source/MentraBluetoothSDK.swift +2439 -0
- package/ios/Source/ObservableStore.swift +88 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/controllers/ControllerManager.swift +187 -0
- package/ios/Source/controllers/R1.swift +827 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Frame.swift +651 -0
- package/ios/Source/sgcs/G1.swift +2505 -0
- package/ios/Source/sgcs/G2.swift +3730 -0
- package/ios/Source/sgcs/Mach1.swift +543 -0
- package/ios/Source/sgcs/MentraLive.swift +4708 -0
- package/ios/Source/sgcs/MentraNex.swift +2479 -0
- package/ios/Source/sgcs/SGCManager.swift +243 -0
- package/ios/Source/sgcs/Simulated.swift +270 -0
- package/ios/Source/sgcs/mentraos_ble.pb.swift +3811 -0
- package/ios/Source/stt/STTTools.swift +154 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +401 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/Constants.swift +74 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2067 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunker.swift +164 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +226 -0
- package/ios/Source/utils/TarBz2Extractor.swift +206 -0
- package/package.json +78 -0
- package/plugin/build/index.d.ts +6 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +4 -0
- package/plugin/build/withAndroid.js +80 -0
- package/plugin/build/withIos.d.ts +4 -0
- package/plugin/build/withIos.js +64 -0
- package/src/BluetoothSdk.types.ts +581 -0
- package/src/BluetoothSdkModule.ts +259 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.stt
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import com.k2fsa.sherpa.onnx.*
|
|
9
|
+
import com.mentra.bluetoothsdk.Bridge
|
|
10
|
+
import java.io.File
|
|
11
|
+
import java.nio.ByteBuffer
|
|
12
|
+
import java.nio.ByteOrder
|
|
13
|
+
import java.util.concurrent.ArrayBlockingQueue
|
|
14
|
+
import java.util.concurrent.BlockingQueue
|
|
15
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* SherpaOnnxTranscriber handles real-time audio transcription using Sherpa-ONNX.
|
|
19
|
+
*
|
|
20
|
+
* It works fully offline and processes PCM audio in real-time to provide partial and final ASR
|
|
21
|
+
* results. This class runs on a background thread, processes short PCM chunks, and emits
|
|
22
|
+
* transcribed text using a listener.
|
|
23
|
+
*
|
|
24
|
+
* Ported from iOS SherpaOnnxTranscriber.swift to match iOS functionality 1:1
|
|
25
|
+
*/
|
|
26
|
+
class SherpaOnnxTranscriber(private val context: Context) {
|
|
27
|
+
companion object {
|
|
28
|
+
private const val TAG = "SherpaOnnxTranscriber"
|
|
29
|
+
private const val SAMPLE_RATE = 16000 // Sherpa-ONNX model's required sample rate
|
|
30
|
+
private const val QUEUE_CAPACITY = 100 // Max number of audio buffers to keep in queue
|
|
31
|
+
private const val PREFS_NAME = "MentraPrefs"
|
|
32
|
+
private const val KEY_STT_MODEL_PATH = "STTModelPath"
|
|
33
|
+
private const val KEY_STT_MODEL_LANGUAGE = "STTModelLanguageCode"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Interface to receive transcription results from Sherpa-ONNX */
|
|
37
|
+
interface TranscriptListener {
|
|
38
|
+
/** Called with live partial transcription (not final yet) */
|
|
39
|
+
fun onPartialResult(text: String, language: String)
|
|
40
|
+
|
|
41
|
+
/** Called when an utterance ends and final text is available */
|
|
42
|
+
fun onFinalResult(text: String, language: String)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private val pcmQueue: BlockingQueue<ByteArray> = ArrayBlockingQueue(QUEUE_CAPACITY)
|
|
46
|
+
private val running = AtomicBoolean(false)
|
|
47
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
48
|
+
|
|
49
|
+
private var workerThread: Thread? = null
|
|
50
|
+
private var recognizer: OnlineRecognizer? = null
|
|
51
|
+
private var stream: OnlineStream? = null
|
|
52
|
+
|
|
53
|
+
private var lastPartialResult = ""
|
|
54
|
+
@Volatile private var transcriptListener: TranscriptListener? = null
|
|
55
|
+
|
|
56
|
+
private val restartLock = Any()
|
|
57
|
+
private var restartRunning = false
|
|
58
|
+
|
|
59
|
+
private val prefs: SharedPreferences by lazy {
|
|
60
|
+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the custom model path from SharedPreferences Resolves relative paths from Documents
|
|
65
|
+
* directory
|
|
66
|
+
*/
|
|
67
|
+
private fun getCustomModelPath(): String? {
|
|
68
|
+
val storedPath = prefs.getString(KEY_STT_MODEL_PATH, null) ?: return null
|
|
69
|
+
|
|
70
|
+
// Always resolve to absolute path if it contains "Documents/"
|
|
71
|
+
// This handles app ID changes between dev builds
|
|
72
|
+
if (storedPath.contains("/Documents/")) {
|
|
73
|
+
val documentsDir =
|
|
74
|
+
context.filesDir // In Android, this is the app's private files directory
|
|
75
|
+
val relativePath = storedPath.substringAfter("/Documents/")
|
|
76
|
+
val fixedPath = File(documentsDir, relativePath).absolutePath
|
|
77
|
+
Bridge.log("Reconstructed STTModelPath: $fixedPath")
|
|
78
|
+
return fixedPath
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Bridge.log("STTModelPath (raw): $storedPath")
|
|
82
|
+
return storedPath
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Get the model language code from SharedPreferences */
|
|
86
|
+
private fun getModelLanguage(): String {
|
|
87
|
+
return prefs.getString(KEY_STT_MODEL_LANGUAGE, "en-US") ?: "en-US"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Initialize the Sherpa-ONNX recognizer. Loads models and configuration, sets up processing
|
|
92
|
+
* thread.
|
|
93
|
+
*/
|
|
94
|
+
fun initialize() {
|
|
95
|
+
try {
|
|
96
|
+
val modelPath = getCustomModelPath()
|
|
97
|
+
|
|
98
|
+
if (modelPath == null) {
|
|
99
|
+
Bridge.log("No Sherpa ONNX model available. Transcription will be disabled.")
|
|
100
|
+
Bridge.log("Please download a model using the model downloader in settings.")
|
|
101
|
+
recognizer = null
|
|
102
|
+
stream = null
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
val modelDir = File(modelPath)
|
|
107
|
+
if (!modelDir.exists() || !modelDir.isDirectory) {
|
|
108
|
+
Bridge.log("Model path does not exist or is not a directory: $modelPath")
|
|
109
|
+
recognizer = null
|
|
110
|
+
stream = null
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
val tokensPath = File(modelDir, "tokens.txt").absolutePath
|
|
115
|
+
val tokensFile = File(tokensPath)
|
|
116
|
+
|
|
117
|
+
if (!tokensFile.exists()) {
|
|
118
|
+
throw IllegalStateException("tokens.txt not found at path: $modelPath")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
val modelType: String
|
|
122
|
+
val modelConfig = OnlineModelConfig()
|
|
123
|
+
|
|
124
|
+
// Detect model type based on available files
|
|
125
|
+
val ctcModelFile = File(modelDir, "model.int8.onnx")
|
|
126
|
+
val transducerEncoderFile = File(modelDir, "encoder.onnx")
|
|
127
|
+
|
|
128
|
+
when {
|
|
129
|
+
ctcModelFile.exists() -> {
|
|
130
|
+
// CTC model detected
|
|
131
|
+
modelType = "ctc"
|
|
132
|
+
Bridge.log("Detected CTC model at $modelPath")
|
|
133
|
+
|
|
134
|
+
val nemoCtc = OnlineNeMoCtcModelConfig()
|
|
135
|
+
nemoCtc.model = ctcModelFile.absolutePath
|
|
136
|
+
|
|
137
|
+
modelConfig.tokens = tokensPath
|
|
138
|
+
modelConfig.numThreads = 1
|
|
139
|
+
modelConfig.neMoCtc = nemoCtc
|
|
140
|
+
}
|
|
141
|
+
transducerEncoderFile.exists() -> {
|
|
142
|
+
// Transducer model detected
|
|
143
|
+
modelType = "transducer"
|
|
144
|
+
Bridge.log("Detected transducer model at $modelPath")
|
|
145
|
+
|
|
146
|
+
val decoderFile = File(modelDir, "decoder.onnx")
|
|
147
|
+
val joinerFile = File(modelDir, "joiner.onnx")
|
|
148
|
+
|
|
149
|
+
if (!decoderFile.exists() || !joinerFile.exists()) {
|
|
150
|
+
throw IllegalStateException(
|
|
151
|
+
"Transducer model files incomplete at path: $modelPath"
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
val transducer = OnlineTransducerModelConfig()
|
|
156
|
+
transducer.encoder = transducerEncoderFile.absolutePath
|
|
157
|
+
transducer.decoder = decoderFile.absolutePath
|
|
158
|
+
transducer.joiner = joinerFile.absolutePath
|
|
159
|
+
|
|
160
|
+
modelConfig.tokens = tokensPath
|
|
161
|
+
modelConfig.transducer = transducer
|
|
162
|
+
modelConfig.numThreads = 1
|
|
163
|
+
}
|
|
164
|
+
else -> {
|
|
165
|
+
throw IllegalStateException("No valid model files found at path: $modelPath")
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Configure recognizer
|
|
170
|
+
val config = OnlineRecognizerConfig()
|
|
171
|
+
config.modelConfig = modelConfig
|
|
172
|
+
config.decodingMethod = "greedy_search"
|
|
173
|
+
config.enableEndpoint = true
|
|
174
|
+
|
|
175
|
+
// Create recognizer (pass null for AssetManager since we're using file paths)
|
|
176
|
+
recognizer =
|
|
177
|
+
try {
|
|
178
|
+
OnlineRecognizer(null, config)
|
|
179
|
+
} catch (e: Exception) {
|
|
180
|
+
Log.e(
|
|
181
|
+
TAG,
|
|
182
|
+
"Failed to create OnlineRecognizer - model file may be corrupted or incomplete",
|
|
183
|
+
e
|
|
184
|
+
)
|
|
185
|
+
null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (recognizer == null) {
|
|
189
|
+
throw IllegalStateException("Failed to create recognizer")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
stream = recognizer?.createStream("")
|
|
193
|
+
|
|
194
|
+
startProcessingThread()
|
|
195
|
+
running.set(true)
|
|
196
|
+
|
|
197
|
+
Bridge.log("Sherpa-ONNX ASR initialized successfully with $modelType model")
|
|
198
|
+
} catch (e: Exception) {
|
|
199
|
+
Bridge.log("Failed to initialize Sherpa-ONNX: ${e.message}")
|
|
200
|
+
Log.e(TAG, "Failed to initialize Sherpa-ONNX", e)
|
|
201
|
+
|
|
202
|
+
// Clean up any partially initialized resources
|
|
203
|
+
stream?.let {
|
|
204
|
+
try {
|
|
205
|
+
it.release()
|
|
206
|
+
} catch (releaseEx: Exception) {
|
|
207
|
+
Log.e(TAG, "Error releasing stream after initialization failure", releaseEx)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
stream = null
|
|
211
|
+
|
|
212
|
+
recognizer?.let {
|
|
213
|
+
try {
|
|
214
|
+
it.release()
|
|
215
|
+
} catch (releaseEx: Exception) {
|
|
216
|
+
Log.e(TAG, "Error releasing recognizer after initialization failure", releaseEx)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
recognizer = null
|
|
220
|
+
|
|
221
|
+
running.set(false)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Feed PCM audio data (16-bit little endian) into the transcriber. This method should be called
|
|
227
|
+
* continuously with short chunks (e.g., 100-300ms).
|
|
228
|
+
*
|
|
229
|
+
* Note: Audio passed to this method is assumed to have already passed VAD elsewhere, so it's
|
|
230
|
+
* directly queued for processing without additional VAD checks.
|
|
231
|
+
*/
|
|
232
|
+
fun acceptAudio(pcm16le: ByteArray) {
|
|
233
|
+
if (!running.get()) {
|
|
234
|
+
// Bridge.log("⚠️ Ignoring audio - transcriber not running")
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Directly queue the audio data for processing
|
|
239
|
+
if (!pcmQueue.offer(pcm16le.copyOf())) {
|
|
240
|
+
// Queue is full, drop oldest and try again
|
|
241
|
+
pcmQueue.poll()
|
|
242
|
+
pcmQueue.offer(pcm16le.copyOf())
|
|
243
|
+
Bridge.log("⚠️ Audio queue overflow - dropped buffer")
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Start a background thread to continuously consume audio and decode using Sherpa */
|
|
248
|
+
private fun startProcessingThread() {
|
|
249
|
+
Bridge.log("🚀 Starting Sherpa-ONNX processing thread...")
|
|
250
|
+
|
|
251
|
+
workerThread =
|
|
252
|
+
Thread({ runLoop() }, "SherpaOnnxProcessor").apply {
|
|
253
|
+
isDaemon = true
|
|
254
|
+
start()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Main processing loop that handles transcription in real-time. Pulls audio from queue, feeds
|
|
260
|
+
* into Sherpa, emits partial/final results.
|
|
261
|
+
*/
|
|
262
|
+
private fun runLoop() {
|
|
263
|
+
Bridge.log("🔄 Sherpa-ONNX processing loop started")
|
|
264
|
+
|
|
265
|
+
while (running.get()) {
|
|
266
|
+
try {
|
|
267
|
+
val currentRecognizer = recognizer
|
|
268
|
+
val currentStream = stream
|
|
269
|
+
|
|
270
|
+
if (currentRecognizer == null || currentStream == null) {
|
|
271
|
+
Bridge.log("⚠️ Recognizer or stream not available, skipping audio chunk")
|
|
272
|
+
Thread.sleep(100)
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Pull data from queue with timeout
|
|
277
|
+
val audioData = pcmQueue.poll(50, java.util.concurrent.TimeUnit.MILLISECONDS)
|
|
278
|
+
|
|
279
|
+
if (audioData != null) {
|
|
280
|
+
// Convert PCM to float [-1.0, 1.0]
|
|
281
|
+
val floatBuf = toFloatArray(audioData)
|
|
282
|
+
|
|
283
|
+
// Pass audio data to the Sherpa-ONNX stream
|
|
284
|
+
currentStream.acceptWaveform(floatBuf, SAMPLE_RATE)
|
|
285
|
+
|
|
286
|
+
// Decode continuously while model is ready
|
|
287
|
+
var decodeCount = 0
|
|
288
|
+
while (currentRecognizer.isReady(currentStream)) {
|
|
289
|
+
currentRecognizer.decode(currentStream)
|
|
290
|
+
decodeCount++
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If utterance endpoint detected
|
|
294
|
+
if (currentRecognizer.isEndpoint(currentStream)) {
|
|
295
|
+
val result = currentRecognizer.getResult(currentStream)
|
|
296
|
+
val finalText = result.text.trim()
|
|
297
|
+
|
|
298
|
+
if (finalText.isNotEmpty()) {
|
|
299
|
+
handleTranscriptionResult(finalText, isFinal = true)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
currentRecognizer.reset(currentStream) // Start new utterance
|
|
303
|
+
lastPartialResult = ""
|
|
304
|
+
} else {
|
|
305
|
+
// Emit partial results if changed
|
|
306
|
+
val result = currentRecognizer.getResult(currentStream)
|
|
307
|
+
val partial = result.text.trim()
|
|
308
|
+
|
|
309
|
+
if (partial != lastPartialResult && partial.isNotEmpty()) {
|
|
310
|
+
handleTranscriptionResult(partial, isFinal = false)
|
|
311
|
+
lastPartialResult = partial
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} catch (e: InterruptedException) {
|
|
316
|
+
Thread.currentThread().interrupt()
|
|
317
|
+
Bridge.log("Processing thread interrupted")
|
|
318
|
+
break
|
|
319
|
+
} catch (e: Exception) {
|
|
320
|
+
Log.e(TAG, "Error processing audio", e)
|
|
321
|
+
// Attempt stream reset to recover
|
|
322
|
+
try {
|
|
323
|
+
recognizer?.let { rec -> stream?.let { str -> rec.reset(str) } }
|
|
324
|
+
} catch (resetEx: Exception) {
|
|
325
|
+
Log.e(TAG, "Failed to reset stream after error", resetEx)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
Bridge.log("ASR processing thread stopped")
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Convert 16-bit PCM byte data (little-endian) to float array [-1.0, 1.0] */
|
|
334
|
+
private fun toFloatArray(pcmData: ByteArray): FloatArray {
|
|
335
|
+
val count = pcmData.size / 2
|
|
336
|
+
val samples = FloatArray(count)
|
|
337
|
+
|
|
338
|
+
val buffer = ByteBuffer.wrap(pcmData).order(ByteOrder.LITTLE_ENDIAN)
|
|
339
|
+
|
|
340
|
+
for (i in 0 until count) {
|
|
341
|
+
samples[i] = buffer.short / 32768.0f
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return samples
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Handle transcription results - send to listener on main thread */
|
|
348
|
+
private fun handleTranscriptionResult(text: String, isFinal: Boolean) {
|
|
349
|
+
val language = getModelLanguage()
|
|
350
|
+
|
|
351
|
+
// Forward to listener on main thread
|
|
352
|
+
mainHandler.post {
|
|
353
|
+
transcriptListener?.let { listener ->
|
|
354
|
+
if (isFinal) {
|
|
355
|
+
listener.onFinalResult(text, language)
|
|
356
|
+
} else {
|
|
357
|
+
listener.onPartialResult(text, language)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Stop transcription processing. This shuts down the processing thread and releases Sherpa-ONNX
|
|
365
|
+
* resources.
|
|
366
|
+
*/
|
|
367
|
+
fun shutdown() {
|
|
368
|
+
Bridge.log("🛑 Shutting down SherpaOnnxTranscriber...")
|
|
369
|
+
|
|
370
|
+
running.set(false)
|
|
371
|
+
|
|
372
|
+
workerThread?.let { thread ->
|
|
373
|
+
thread.interrupt()
|
|
374
|
+
try {
|
|
375
|
+
thread.join(500)
|
|
376
|
+
} catch (e: InterruptedException) {
|
|
377
|
+
Thread.currentThread().interrupt()
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
workerThread = null
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
stream?.let {
|
|
384
|
+
Bridge.log("🧹 Cleaning up Sherpa-ONNX stream")
|
|
385
|
+
it.release()
|
|
386
|
+
}
|
|
387
|
+
stream = null
|
|
388
|
+
|
|
389
|
+
recognizer?.let {
|
|
390
|
+
Bridge.log("🧹 Cleaning up Sherpa-ONNX recognizer")
|
|
391
|
+
it.release()
|
|
392
|
+
}
|
|
393
|
+
recognizer = null
|
|
394
|
+
} catch (e: Exception) {
|
|
395
|
+
Log.e(TAG, "Error releasing Sherpa resources", e)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Clear any remaining audio buffers
|
|
399
|
+
val remainingBuffers = pcmQueue.size
|
|
400
|
+
if (remainingBuffers > 0) {
|
|
401
|
+
Bridge.log("🗑️ Clearing $remainingBuffers remaining audio buffers")
|
|
402
|
+
}
|
|
403
|
+
pcmQueue.clear()
|
|
404
|
+
|
|
405
|
+
Bridge.log("✅ SherpaOnnxTranscriber shutdown complete")
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Restarts the transcriber after a model change. Shuts down existing resources, clears buffers,
|
|
410
|
+
* and reinitializes the recognizer.
|
|
411
|
+
*/
|
|
412
|
+
fun restart() {
|
|
413
|
+
synchronized(restartLock) {
|
|
414
|
+
if (restartRunning) {
|
|
415
|
+
Bridge.log("Restart already in progress, skipping")
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
restartRunning = true
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
Bridge.log("♻️ Restarting SherpaOnnxTranscriber...")
|
|
423
|
+
shutdown()
|
|
424
|
+
// Small delay to ensure cleanup completes
|
|
425
|
+
Thread.sleep(100)
|
|
426
|
+
initialize()
|
|
427
|
+
} catch (e: Exception) {
|
|
428
|
+
Log.e(TAG, "Error during transcriber restart", e)
|
|
429
|
+
} finally {
|
|
430
|
+
synchronized(restartLock) { restartRunning = false }
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Register a listener to receive partial and final transcription updates */
|
|
435
|
+
fun setTranscriptListener(listener: TranscriptListener?) {
|
|
436
|
+
transcriptListener = listener
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Check if the transcriber was successfully initialized */
|
|
440
|
+
fun isInitialized(): Boolean {
|
|
441
|
+
return recognizer != null && stream != null
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Handle microphone state changes */
|
|
445
|
+
fun microphoneStateChanged(state: Boolean) {
|
|
446
|
+
if (!state) {
|
|
447
|
+
// Microphone turned off - clear queue and reset stream
|
|
448
|
+
pcmQueue.clear()
|
|
449
|
+
|
|
450
|
+
recognizer?.let { rec ->
|
|
451
|
+
stream?.let { str ->
|
|
452
|
+
try {
|
|
453
|
+
rec.reset(str)
|
|
454
|
+
lastPartialResult = ""
|
|
455
|
+
Log.d(TAG, "Microphone off — stream reset")
|
|
456
|
+
} catch (e: Exception) {
|
|
457
|
+
Log.e(TAG, "Error resetting stream on mic off", e)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
Log.d(TAG, "Microphone on")
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.stt
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import com.konovalov.vad.silero.Vad
|
|
6
|
+
import com.konovalov.vad.silero.VadSilero
|
|
7
|
+
import com.konovalov.vad.silero.config.FrameSize
|
|
8
|
+
import com.konovalov.vad.silero.config.Mode
|
|
9
|
+
import com.konovalov.vad.silero.config.SampleRate
|
|
10
|
+
import java.nio.ByteBuffer
|
|
11
|
+
import java.nio.ByteOrder
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* VadGateSpeechPolicy handles Voice Activity Detection using Silero VAD.
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - Dynamic VAD check throttling (adjusts frequency based on silence duration)
|
|
18
|
+
* - 8-second silence timeout before declaring "not speaking"
|
|
19
|
+
* - Bypass modes for debugging and PCM streaming
|
|
20
|
+
* - 512-sample frame size (matches Sherpa-ONNX requirements)
|
|
21
|
+
*
|
|
22
|
+
* Ported from android_core VadGateSpeechPolicy.java
|
|
23
|
+
*/
|
|
24
|
+
class VadGateSpeechPolicy(private val context: Context) {
|
|
25
|
+
companion object {
|
|
26
|
+
private const val TAG = "VadGateSpeechPolicy"
|
|
27
|
+
|
|
28
|
+
// Total required silence duration
|
|
29
|
+
private const val REQUIRED_SILENCE_DURATION_MS = 8000L
|
|
30
|
+
|
|
31
|
+
// Dynamic VAD check intervals
|
|
32
|
+
private const val INITIAL_SILENCE_VAD_INTERVAL_MS = 50L // Check frequently at first
|
|
33
|
+
private const val MEDIUM_SILENCE_VAD_INTERVAL_MS = 100L // Medium frequency after some time
|
|
34
|
+
private const val LONG_SILENCE_VAD_INTERVAL_MS =
|
|
35
|
+
200L // Less frequent after extended silence
|
|
36
|
+
|
|
37
|
+
// Thresholds for switching intervals
|
|
38
|
+
private const val MEDIUM_SILENCE_THRESHOLD_MS =
|
|
39
|
+
20000L // Switch to medium interval after 20s
|
|
40
|
+
private const val LONG_SILENCE_THRESHOLD_MS = 60000L // Switch to long interval after 60s
|
|
41
|
+
|
|
42
|
+
private const val FRAME_SIZE = 512
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private var vad: VadSilero? = null
|
|
46
|
+
private var isCurrentlySpeech = false
|
|
47
|
+
private var bypassVadForDebugging = false
|
|
48
|
+
private var bypassVadForPCM = false
|
|
49
|
+
|
|
50
|
+
// Timestamp of the last detected speech
|
|
51
|
+
private var lastSpeechDetectedTime = 0L
|
|
52
|
+
// Throttle timer for silence VAD checks
|
|
53
|
+
private var lastVadCheckTime = 0L
|
|
54
|
+
|
|
55
|
+
/** Initialize the Silero VAD model */
|
|
56
|
+
fun init(blockSizeSamples: Int) {
|
|
57
|
+
startVad()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun startVad() {
|
|
61
|
+
try {
|
|
62
|
+
// Set internal silence duration very low; external logic manages the full required
|
|
63
|
+
// silence duration
|
|
64
|
+
vad =
|
|
65
|
+
Vad.builder()
|
|
66
|
+
.setContext(context)
|
|
67
|
+
.setSampleRate(SampleRate.SAMPLE_RATE_16K)
|
|
68
|
+
.setFrameSize(FrameSize.FRAME_SIZE_512)
|
|
69
|
+
.setMode(Mode.NORMAL)
|
|
70
|
+
.setSilenceDurationMs(50)
|
|
71
|
+
.setSpeechDurationMs(50)
|
|
72
|
+
.build()
|
|
73
|
+
|
|
74
|
+
Log.d(TAG, "VAD initialized successfully")
|
|
75
|
+
} catch (e: Exception) {
|
|
76
|
+
Log.e(TAG, "Failed to initialize VAD", e)
|
|
77
|
+
vad = null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Check if audio should be passed to the recognizer based on VAD state */
|
|
82
|
+
fun shouldPassAudioToRecognizer(): Boolean {
|
|
83
|
+
// CRITICAL: Handle VAD null case
|
|
84
|
+
if (vad == null) {
|
|
85
|
+
return bypassVadForDebugging || bypassVadForPCM || true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return bypassVadForDebugging || bypassVadForPCM || isCurrentlySpeech
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Process audio bytes through VAD to detect speech */
|
|
92
|
+
fun processAudioBytes(bytes: ByteArray, offset: Int, length: Int) {
|
|
93
|
+
val now = System.currentTimeMillis()
|
|
94
|
+
|
|
95
|
+
// If in speech state and it hasn't been 8 seconds since the last speech was detected, skip
|
|
96
|
+
// processing
|
|
97
|
+
if (isCurrentlySpeech && (now - lastSpeechDetectedTime < REQUIRED_SILENCE_DURATION_MS)) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Calculate silence duration
|
|
102
|
+
val silenceDuration = now - lastSpeechDetectedTime
|
|
103
|
+
|
|
104
|
+
// Determine which VAD interval to use based on silence duration
|
|
105
|
+
val currentVadInterval =
|
|
106
|
+
when {
|
|
107
|
+
silenceDuration < MEDIUM_SILENCE_THRESHOLD_MS -> INITIAL_SILENCE_VAD_INTERVAL_MS
|
|
108
|
+
silenceDuration < LONG_SILENCE_THRESHOLD_MS -> MEDIUM_SILENCE_VAD_INTERVAL_MS
|
|
109
|
+
else -> LONG_SILENCE_VAD_INTERVAL_MS
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// During silence, throttle VAD checks based on the dynamic interval
|
|
113
|
+
if (!isCurrentlySpeech && (now - lastVadCheckTime < currentVadInterval)) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
lastVadCheckTime = now
|
|
117
|
+
|
|
118
|
+
val audioBytesFull = bytesToShort(bytes)
|
|
119
|
+
val totalSamples = audioBytesFull.size
|
|
120
|
+
|
|
121
|
+
if (totalSamples % FRAME_SIZE != 0) {
|
|
122
|
+
Log.e(
|
|
123
|
+
TAG,
|
|
124
|
+
"Invalid audio frame size: $totalSamples samples. Needs to be multiple of $FRAME_SIZE."
|
|
125
|
+
)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
val currentVad =
|
|
130
|
+
vad
|
|
131
|
+
?: run {
|
|
132
|
+
Log.w(TAG, "VAD not initialized, skipping audio processing")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var previousSpeechState = isCurrentlySpeech
|
|
137
|
+
|
|
138
|
+
// Process each 512-sample frame
|
|
139
|
+
val numFrames = totalSamples / FRAME_SIZE
|
|
140
|
+
for (i in 0 until numFrames) {
|
|
141
|
+
val currentTime = System.currentTimeMillis()
|
|
142
|
+
val startIdx = i * FRAME_SIZE
|
|
143
|
+
val audioFrame = audioBytesFull.copyOfRange(startIdx, startIdx + FRAME_SIZE)
|
|
144
|
+
val detectedSpeech = currentVad.isSpeech(audioFrame)
|
|
145
|
+
|
|
146
|
+
if (detectedSpeech) {
|
|
147
|
+
isCurrentlySpeech = true
|
|
148
|
+
// Update the last speech detection timestamp
|
|
149
|
+
lastSpeechDetectedTime = currentTime
|
|
150
|
+
} else {
|
|
151
|
+
// If no speech detected, and 8 seconds have elapsed since the last speech, mark as
|
|
152
|
+
// silence
|
|
153
|
+
if (currentTime - lastSpeechDetectedTime >= REQUIRED_SILENCE_DURATION_MS) {
|
|
154
|
+
isCurrentlySpeech = false
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (isCurrentlySpeech != previousSpeechState) {
|
|
159
|
+
Log.d(
|
|
160
|
+
TAG,
|
|
161
|
+
"Speech detection changed to: ${if (isCurrentlySpeech) "SPEECH" else "SILENCE"}"
|
|
162
|
+
)
|
|
163
|
+
previousSpeechState = isCurrentlySpeech
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Convert byte array (PCM16LE) to short array */
|
|
169
|
+
private fun bytesToShort(bytes: ByteArray): ShortArray {
|
|
170
|
+
val shorts = ShortArray(bytes.size / 2)
|
|
171
|
+
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts)
|
|
172
|
+
return shorts
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Stop VAD and release resources */
|
|
176
|
+
fun stop() {
|
|
177
|
+
try {
|
|
178
|
+
vad?.close()
|
|
179
|
+
vad = null
|
|
180
|
+
Log.d(TAG, "VAD stopped and resources released")
|
|
181
|
+
} catch (e: Exception) {
|
|
182
|
+
Log.e(TAG, "Error stopping VAD", e)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Reset VAD state */
|
|
187
|
+
fun reset() {
|
|
188
|
+
isCurrentlySpeech = false
|
|
189
|
+
lastSpeechDetectedTime = 0
|
|
190
|
+
lastVadCheckTime = 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Change bypass VAD for debugging state */
|
|
194
|
+
fun changeBypassVadForDebugging(bypass: Boolean) {
|
|
195
|
+
bypassVadForDebugging = bypass
|
|
196
|
+
Log.d(TAG, "Bypass VAD for debugging: $bypass")
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Change bypass VAD for PCM streaming state */
|
|
200
|
+
fun changeBypassVadForPCM(bypass: Boolean) {
|
|
201
|
+
Log.d(TAG, "VAD PCM Bypass State Change: $bypassVadForPCM -> $bypass")
|
|
202
|
+
bypassVadForPCM = bypass
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Handle microphone state changes */
|
|
206
|
+
fun microphoneStateChanged(state: Boolean) {
|
|
207
|
+
if (!state) {
|
|
208
|
+
// Microphone turned off: force immediate silence
|
|
209
|
+
isCurrentlySpeech = false
|
|
210
|
+
lastSpeechDetectedTime = 0
|
|
211
|
+
lastVadCheckTime = 0
|
|
212
|
+
|
|
213
|
+
// Optionally flush the VAD's internal state by processing a silent frame
|
|
214
|
+
vad?.let { vadInstance ->
|
|
215
|
+
val silentFrame = ShortArray(FRAME_SIZE)
|
|
216
|
+
vadInstance.isSpeech(silentFrame)
|
|
217
|
+
}
|
|
218
|
+
Log.d(TAG, "Microphone turned off; forced state to SILENCE.")
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|