@glassly/bluetooth-sdk 0.1.0-dev.26
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 +563 -0
- package/android/build.gradle +450 -0
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +212 -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/glassly/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 +4 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +50 -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/AndroidManifest.xml +42 -0
- package/android/src/main/assets/G2Firmware/README.md +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1088 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2528 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +360 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +8 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +937 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3878 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6428 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1241 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1280 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10817 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2011 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/app.plugin.js +3 -0
- package/build/BluetoothSdk.types.d.ts +1354 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +41 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/_internal.d.ts +17 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +16 -0
- package/build/_internal.js.map +1 -0
- package/build/_private/BluetoothSdkModule.d.ts +172 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/_private/BluetoothSdkModule.js +300 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/_private/LocalNetworkModule.d.ts +38 -0
- package/build/_private/LocalNetworkModule.d.ts.map +1 -0
- package/build/_private/LocalNetworkModule.js +3 -0
- package/build/_private/LocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts +4 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -0
- package/build/_private/cameraRequestPayload.js +32 -0
- package/build/_private/cameraRequestPayload.js.map +1 -0
- package/build/_private/photoRequestPayload.d.ts +6 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +86 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/_private/sdkOtaManifest.d.ts +11 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +15 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +46 -0
- package/build/changelogs.js.map +1 -0
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +9 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +12 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +171 -0
- package/build/index.js.map +1 -0
- package/build/ota-server/OtaServer.types.d.ts +14 -0
- package/build/ota-server/OtaServer.types.d.ts.map +1 -0
- package/build/ota-server/OtaServer.types.js +2 -0
- package/build/ota-server/OtaServer.types.js.map +1 -0
- package/build/ota-server/OtaServerModule.d.ts +21 -0
- package/build/ota-server/OtaServerModule.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.js +3 -0
- package/build/ota-server/OtaServerModule.js.map +1 -0
- package/build/ota-server/OtaServerModule.web.d.ts +10 -0
- package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.web.js +12 -0
- package/build/ota-server/OtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.js +2 -0
- package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.js +3 -0
- package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
- package/build/photo-receiver/index.d.ts +3 -0
- package/build/photo-receiver/index.d.ts.map +1 -0
- package/build/photo-receiver/index.js +3 -0
- package/build/photo-receiver/index.js.map +1 -0
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +4 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetooth.d.ts +100 -0
- package/build/react/useBluetooth.d.ts.map +1 -0
- package/build/react/useBluetooth.js +167 -0
- package/build/react/useBluetooth.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +153 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/expo-module.config.json +14 -0
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +1034 -0
- package/ios/GlasslyBluetoothSDK.podspec +106 -0
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/LocalPhotoUploadServer.swift +754 -0
- package/ios/OtaServerModule.swift +147 -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/include/PcmConverter.h +22 -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 +2383 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -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 +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/PhotoReceiverModule.swift +129 -0
- package/ios/Source/BluetoothSDK.swift +2303 -0
- package/ios/Source/BluetoothSDKDebug.swift +12 -0
- package/ios/Source/BluetoothSdkDefaults.swift +29 -0
- package/ios/Source/Bridge.swift +708 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +2109 -0
- package/ios/Source/DeviceStore.swift +336 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +7 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/ObservableStore.swift +118 -0
- package/ios/Source/OtaManifest.swift +211 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/Resources/G2Firmware/README.md +10 -0
- package/ios/Source/audio/AudioModels.swift +178 -0
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +866 -0
- package/ios/Source/connection/ScanSession.swift +27 -0
- package/ios/Source/controllers/ControllerManager.swift +184 -0
- package/ios/Source/controllers/R1.swift +829 -0
- package/ios/Source/errors/BluetoothSdkError.swift +19 -0
- package/ios/Source/events/BluetoothEvents.swift +342 -0
- package/ios/Source/internal/BleTraceLogger.swift +192 -0
- package/ios/Source/internal/BluetoothAvailability.swift +78 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
- package/ios/Source/internal/ValueParsing.swift +99 -0
- package/ios/Source/requests/DisplayRequests.swift +82 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/Frame.swift +649 -0
- package/ios/Source/sgcs/G1.swift +2554 -0
- package/ios/Source/sgcs/G2.swift +6887 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
- package/ios/Source/sgcs/G2CfwRenderer.swift +1279 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
- package/ios/Source/sgcs/G2Flasher.swift +773 -0
- package/ios/Source/sgcs/Mach1.swift +541 -0
- package/ios/Source/sgcs/MentraLive.swift +7011 -0
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/MentraNex.swift +3221 -0
- package/ios/Source/sgcs/Nimo.swift +1886 -0
- package/ios/Source/sgcs/SGCManager.swift +608 -0
- package/ios/Source/sgcs/Simulated.swift +274 -0
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
- package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
- package/ios/Source/status/DeviceStatus.swift +981 -0
- package/ios/Source/status/RuntimeState.swift +359 -0
- package/ios/Source/status/WifiHotspotStatus.swift +345 -0
- package/ios/Source/streaming/StreamModels.swift +693 -0
- package/ios/Source/stt/STTTools.swift +191 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
- package/ios/Source/tts/TTSTools.swift +171 -0
- package/ios/Source/types/DeviceModels.swift +177 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/BinaryCodec.swift +61 -0
- package/ios/Source/utils/BleJsonCompact.swift +405 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/Constants.swift +78 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2096 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
- package/ios/Source/utils/MessageChunker.swift +294 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
- package/ios/Source/utils/TarBz2Extractor.swift +257 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +129 -0
- package/plugin/build/index.d.ts +10 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +3 -0
- package/plugin/build/withAndroid.js +190 -0
- package/plugin/build/withIos.d.ts +5 -0
- package/plugin/build/withIos.js +108 -0
- package/scripts/inject-ios-sdk-version.mjs +65 -0
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +45 -0
- package/scripts/write-release-metadata.mjs +152 -0
- package/scripts/write-release-metadata.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +1623 -0
- package/src/_internal.ts +16 -0
- package/src/_private/BluetoothSdkModule.ts +684 -0
- package/src/_private/LocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +34 -0
- package/src/_private/photoRequestPayload.ts +90 -0
- package/src/_private/sdkOtaManifest.ts +18 -0
- package/src/changelogs.ts +46 -0
- package/src/debug.ts +9 -0
- package/src/generated/changelogCatalog.ts +13 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +321 -0
- package/src/ota-server/OtaServer.types.ts +10 -0
- package/src/ota-server/OtaServerModule.ts +23 -0
- package/src/ota-server/OtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
- package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
- package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
- package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
- package/src/photo-receiver/index.ts +2 -0
- package/src/react/index.ts +21 -0
- package/src/react/useBluetooth.ts +309 -0
- package/src/react/useBluetoothEvent.ts +35 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +202 -0
- package/src/types/index.ts +12 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk.sgcs.ar99.ota;
|
|
2
|
+
|
|
3
|
+
import java.io.ByteArrayOutputStream;
|
|
4
|
+
|
|
5
|
+
public final class OtaProtocol {
|
|
6
|
+
private OtaProtocol() {}
|
|
7
|
+
|
|
8
|
+
public static byte[] buildFrame(byte cmd, byte paramType, byte[] payload) {
|
|
9
|
+
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
10
|
+
out.write(OtaCommandConstants.OTA.SVC_ID & 0xFF);
|
|
11
|
+
out.write(cmd & 0xFF);
|
|
12
|
+
out.write(paramType & 0xFF);
|
|
13
|
+
int len = payload != null ? payload.length : 0;
|
|
14
|
+
out.write(len & 0xFF);
|
|
15
|
+
out.write((len >> 8) & 0xFF);
|
|
16
|
+
if (payload != null && payload.length > 0) {
|
|
17
|
+
try {
|
|
18
|
+
out.write(payload);
|
|
19
|
+
} catch (Exception ignored) {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return out.toByteArray();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static byte[] buildRequestUpgrade(int packageSize, int blockSize, long crc32) {
|
|
26
|
+
// Wire layout matches the vendor bring-up frame: type marker, then TLV
|
|
27
|
+
// 0x0A/len4 = package size (LE). CRC is validated per-block in SEND_IMAGE_DATA;
|
|
28
|
+
// blockSize is negotiated separately in CONNECT_NEGOTIATION.
|
|
29
|
+
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
|
30
|
+
payload.write(0x09);
|
|
31
|
+
payload.write(0x01);
|
|
32
|
+
payload.write(0x00);
|
|
33
|
+
payload.write(0x01);
|
|
34
|
+
payload.write(0x0A);
|
|
35
|
+
payload.write(0x04);
|
|
36
|
+
payload.write(packageSize & 0xFF);
|
|
37
|
+
payload.write((packageSize >> 8) & 0xFF);
|
|
38
|
+
payload.write((packageSize >> 16) & 0xFF);
|
|
39
|
+
payload.write((packageSize >> 24) & 0xFF);
|
|
40
|
+
payload.write(0x00);
|
|
41
|
+
return buildFrame(OtaCommandConstants.OTA.REQUEST_UPGRADE, (byte) 0x80, payload.toByteArray());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static byte[] buildConnectNegotiation(int blockSize) {
|
|
45
|
+
return buildFrame(OtaCommandConstants.OTA.CONNECT_NEGOTIATION, (byte) 0x80, null);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static byte[] buildNegotiationResult() {
|
|
49
|
+
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
|
50
|
+
payload.write(0x01);
|
|
51
|
+
payload.write(0x01);
|
|
52
|
+
payload.write(0x00);
|
|
53
|
+
payload.write(0x01);
|
|
54
|
+
return buildFrame(OtaCommandConstants.OTA.NEGOTIATION_RESULT, (byte) 0x80, payload.toByteArray());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public static byte[] buildSendImageData(int psn, byte[] data) {
|
|
58
|
+
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
|
59
|
+
payload.write(psn & 0xFF);
|
|
60
|
+
long crc32 = OtaCrc32Util.calculate(data);
|
|
61
|
+
payload.write((byte) (crc32 & 0xFF));
|
|
62
|
+
payload.write((byte) ((crc32 >> 8) & 0xFF));
|
|
63
|
+
payload.write((byte) ((crc32 >> 16) & 0xFF));
|
|
64
|
+
payload.write((byte) ((crc32 >> 24) & 0xFF));
|
|
65
|
+
if (data != null && data.length > 0) {
|
|
66
|
+
try {
|
|
67
|
+
payload.write(data);
|
|
68
|
+
} catch (Exception ignored) {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return buildFrame(OtaCommandConstants.OTA.SEND_IMAGE_DATA, (byte) 0x80, payload.toByteArray());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public static FrameHeader parseFrameHeader(byte[] frame) {
|
|
75
|
+
if (frame == null || frame.length < 5) return null;
|
|
76
|
+
FrameHeader header = new FrameHeader();
|
|
77
|
+
header.svcId = frame[0];
|
|
78
|
+
header.cmd = frame[1];
|
|
79
|
+
header.paramType = frame[2];
|
|
80
|
+
header.paramLen = OtaByteUtils.readShortLE(frame, 3) & 0xFFFF;
|
|
81
|
+
header.payloadOffset = 5;
|
|
82
|
+
return header;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public static class FrameHeader {
|
|
86
|
+
public byte svcId;
|
|
87
|
+
public byte cmd;
|
|
88
|
+
public byte paramType;
|
|
89
|
+
public int paramLen;
|
|
90
|
+
public int payloadOffset;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk.speakerid
|
|
2
|
+
|
|
3
|
+
import android.util.Base64
|
|
4
|
+
import com.glassly.bluetoothsdk.Bridge
|
|
5
|
+
import com.k2fsa.sherpa.onnx.SpeakerEmbeddingExtractor
|
|
6
|
+
import com.k2fsa.sherpa.onnx.SpeakerEmbeddingExtractorConfig
|
|
7
|
+
import java.io.File
|
|
8
|
+
import java.nio.ByteBuffer
|
|
9
|
+
import java.nio.ByteOrder
|
|
10
|
+
import java.util.concurrent.Executors
|
|
11
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
12
|
+
import kotlin.math.sqrt
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* On-device speaker identification over the canonical 16 kHz mono PCM stream.
|
|
16
|
+
*
|
|
17
|
+
* Taps DeviceManager.handlePcm (the single audio choke point), windows voiced
|
|
18
|
+
* audio with a local energy gate, computes a speaker embedding per
|
|
19
|
+
* utterance-ish window (sherpa-onnx CAM++ model), matches against enrolled
|
|
20
|
+
* profile centroids by cosine similarity, and emits `speaker_detected` bridge
|
|
21
|
+
* events. Raw audio never leaves the phone for speaker ID; enrollment audio is
|
|
22
|
+
* discarded as soon as its embeddings are computed.
|
|
23
|
+
*
|
|
24
|
+
* Profiles are pushed in from JS (`setSpeakerProfiles`) — MMKV in the engine
|
|
25
|
+
* layer is the source of truth; native holds them in memory only.
|
|
26
|
+
*
|
|
27
|
+
* Ported 1:1 from iOS SpeakerIdEngine.swift.
|
|
28
|
+
*/
|
|
29
|
+
object SpeakerIdEngine {
|
|
30
|
+
private const val TAG = "SpeakerIdEngine"
|
|
31
|
+
|
|
32
|
+
// Benchmarked cross-speaker ceiling for this model is ~0.46 (the MAX over
|
|
33
|
+
// clean-audio pairs; typical impostors score far lower), while on-device
|
|
34
|
+
// same-speaker scores run below the 0.67+ clean bench. Owner-only voice
|
|
35
|
+
// recognition favors recall: a borderline accept just labels a line "You",
|
|
36
|
+
// and the JS segment resolver's overlap voting + conflict damping still
|
|
37
|
+
// filter isolated stray windows — so sit at the edge of the impostor range
|
|
38
|
+
// rather than above it. (With one enrolled profile the margin rule is
|
|
39
|
+
// vacuous.)
|
|
40
|
+
private const val MATCH_THRESHOLD = 0.45f
|
|
41
|
+
private const val MATCH_MARGIN = 0.08f
|
|
42
|
+
private const val WINDOW_VOICED_TARGET_MS = 3000.0
|
|
43
|
+
private const val END_OF_SPEECH_SILENCE_MS = 500.0
|
|
44
|
+
private const val MIN_VOICED_MS = 1000.0
|
|
45
|
+
private const val MAX_WINDOW_SAMPLES = 16000 * 10
|
|
46
|
+
private const val VOICED_RMS_THRESHOLD = 0.01f
|
|
47
|
+
private const val ENROLL_CONSISTENCY_THRESHOLD = 0.6f
|
|
48
|
+
private const val ENROLL_CHUNK_COUNT = 3
|
|
49
|
+
|
|
50
|
+
private const val SAMPLE_RATE = 16000
|
|
51
|
+
private const val PREFS_NAME = "GlasslyPrefs"
|
|
52
|
+
private const val KEY_MODEL_PATH = "SpeakerIdModelPath"
|
|
53
|
+
|
|
54
|
+
data class Profile(val id: String, val name: String, val embedding: FloatArray)
|
|
55
|
+
|
|
56
|
+
data class Match(
|
|
57
|
+
val profileId: String,
|
|
58
|
+
val name: String,
|
|
59
|
+
val similarity: Float,
|
|
60
|
+
/** Epoch ms when the window producing this match ended. */
|
|
61
|
+
val endMs: Long,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
private val executor = Executors.newSingleThreadExecutor { runnable ->
|
|
65
|
+
Thread(runnable, "SpeakerIdProcessor").apply { isDaemon = true }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private val identifyEnabled = AtomicBoolean(false)
|
|
69
|
+
private val enrolling = AtomicBoolean(false)
|
|
70
|
+
|
|
71
|
+
// Everything below is touched on the executor thread only.
|
|
72
|
+
private var extractor: SpeakerEmbeddingExtractor? = null
|
|
73
|
+
private var extractorLoadAttempted = false
|
|
74
|
+
@Volatile private var profiles: List<Profile> = emptyList()
|
|
75
|
+
|
|
76
|
+
private var windowSamples = ArrayList<Float>()
|
|
77
|
+
private var windowStartMs = 0L
|
|
78
|
+
private var windowVoicedMs = 0.0
|
|
79
|
+
private var trailingSilenceMs = 0.0
|
|
80
|
+
|
|
81
|
+
private class EnrollmentSession(
|
|
82
|
+
val targetVoicedMs: Double,
|
|
83
|
+
val source: String,
|
|
84
|
+
) {
|
|
85
|
+
val samples = ArrayList<Float>()
|
|
86
|
+
var voicedMs = 0.0
|
|
87
|
+
var lastProgressAt = 0L
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private var enrollment: EnrollmentSession? = null
|
|
91
|
+
private var enrollmentEnded: (() -> Unit)? = null
|
|
92
|
+
|
|
93
|
+
@Volatile private var lastMatch: Match? = null
|
|
94
|
+
|
|
95
|
+
// MARK: - Public API (any thread)
|
|
96
|
+
|
|
97
|
+
fun setIdentifyEnabled(enabled: Boolean) {
|
|
98
|
+
identifyEnabled.set(enabled)
|
|
99
|
+
Bridge.log("SpeakerID: identify ${if (enabled) "enabled" else "disabled"}")
|
|
100
|
+
executor.execute {
|
|
101
|
+
if (enabled) ensureExtractor() else resetWindow()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fun setProfiles(next: List<Profile>) {
|
|
106
|
+
profiles = next
|
|
107
|
+
Bridge.log(
|
|
108
|
+
"SpeakerID: profiles set — ${next.size}: " +
|
|
109
|
+
next.joinToString(", ") { "${it.name}(${it.embedding.size}d)" }
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fun setModelPath(path: String) {
|
|
114
|
+
try {
|
|
115
|
+
val prefs = Bridge.getContext().getSharedPreferences(PREFS_NAME, android.content.Context.MODE_PRIVATE)
|
|
116
|
+
prefs.edit().putString(KEY_MODEL_PATH, path).apply()
|
|
117
|
+
Bridge.log("SpeakerID: model path set → $path")
|
|
118
|
+
} catch (e: Exception) {
|
|
119
|
+
Bridge.log("SpeakerID: failed to persist model path: ${e.message}")
|
|
120
|
+
}
|
|
121
|
+
executor.execute {
|
|
122
|
+
// Force a reload on next use — the model may have just arrived.
|
|
123
|
+
extractor?.release()
|
|
124
|
+
extractor = null
|
|
125
|
+
extractorLoadAttempted = false
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fun isModelAvailable(): Boolean {
|
|
130
|
+
val path = resolvedModelPath() ?: return false
|
|
131
|
+
return File(path).let { it.exists() && it.length() > 0 }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* [onEnded] fires (on the engine thread) whenever the enrollment session
|
|
136
|
+
* finishes or fails, so the caller can release the mic requirement.
|
|
137
|
+
*/
|
|
138
|
+
fun startEnrollment(targetSpeechSeconds: Double, source: String, onEnded: () -> Unit) {
|
|
139
|
+
enrolling.set(true)
|
|
140
|
+
executor.execute {
|
|
141
|
+
ensureExtractor()
|
|
142
|
+
if (extractor == null) {
|
|
143
|
+
Bridge.log("SpeakerID: enrollment aborted — model unavailable")
|
|
144
|
+
finishEnrollment(ok = false, reason = "model_unavailable", embeddings = emptyList(), source = source)
|
|
145
|
+
onEnded()
|
|
146
|
+
return@execute
|
|
147
|
+
}
|
|
148
|
+
Bridge.log("SpeakerID: enrollment started — target ${targetSpeechSeconds}s voiced, mic=$source")
|
|
149
|
+
enrollmentEnded = onEnded
|
|
150
|
+
enrollment = EnrollmentSession(
|
|
151
|
+
targetVoicedMs = maxOf(5.0, targetSpeechSeconds) * 1000.0,
|
|
152
|
+
source = source,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fun cancelEnrollment() {
|
|
158
|
+
enrolling.set(false)
|
|
159
|
+
executor.execute {
|
|
160
|
+
enrollment = null
|
|
161
|
+
val onEnded = enrollmentEnded
|
|
162
|
+
enrollmentEnded = null
|
|
163
|
+
onEnded?.invoke()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Latest accepted identification, if it ended within [withinMs] of now.
|
|
169
|
+
* Used by the local STT path to stamp transcripts (native-side join).
|
|
170
|
+
*/
|
|
171
|
+
fun recentMatch(withinMs: Long): Match? {
|
|
172
|
+
val match = lastMatch ?: return null
|
|
173
|
+
return if (System.currentTimeMillis() - match.endMs <= withinMs) match else null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Called from DeviceManager.handlePcm with 16 kHz mono s16le PCM. */
|
|
177
|
+
fun acceptAudio(pcm16le: ByteArray) {
|
|
178
|
+
if (!identifyEnabled.get() && !enrolling.get()) return
|
|
179
|
+
if (pcm16le.size < 2) return
|
|
180
|
+
val copy = pcm16le.copyOf()
|
|
181
|
+
executor.execute { process(copy) }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// MARK: - Processing (executor thread only)
|
|
185
|
+
|
|
186
|
+
private fun process(pcmData: ByteArray) {
|
|
187
|
+
val samples = toFloatArray(pcmData)
|
|
188
|
+
if (samples.isEmpty()) return
|
|
189
|
+
val chunkMs = samples.size.toDouble() / SAMPLE_RATE * 1000.0
|
|
190
|
+
val rms = rms(samples)
|
|
191
|
+
val voiced = rms > VOICED_RMS_THRESHOLD
|
|
192
|
+
|
|
193
|
+
if (enrollment != null) {
|
|
194
|
+
processEnrollment(samples, chunkMs, rms, voiced)
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
if (!identifyEnabled.get()) return
|
|
198
|
+
processIdentification(samples, chunkMs, voiced)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private fun processIdentification(samples: FloatArray, chunkMs: Double, voiced: Boolean) {
|
|
202
|
+
val nowMs = System.currentTimeMillis()
|
|
203
|
+
|
|
204
|
+
if (windowSamples.isEmpty()) {
|
|
205
|
+
if (!voiced) return
|
|
206
|
+
windowStartMs = nowMs - chunkMs.toLong()
|
|
207
|
+
windowVoicedMs = 0.0
|
|
208
|
+
trailingSilenceMs = 0.0
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Accumulate VOICED samples only — enrollment computes centroids from
|
|
212
|
+
// voiced-only audio, so identification must embed like-for-like.
|
|
213
|
+
// Windows padded with inter-phrase silence score far below the same
|
|
214
|
+
// speaker's centroid (observed 0.1–0.48 vs the 0.67+ bench).
|
|
215
|
+
if (voiced) {
|
|
216
|
+
if (windowSamples.size < MAX_WINDOW_SAMPLES) {
|
|
217
|
+
windowSamples.ensureCapacity(windowSamples.size + samples.size)
|
|
218
|
+
for (s in samples) windowSamples.add(s)
|
|
219
|
+
}
|
|
220
|
+
windowVoicedMs += chunkMs
|
|
221
|
+
trailingSilenceMs = 0.0
|
|
222
|
+
} else {
|
|
223
|
+
trailingSilenceMs += chunkMs
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
val endOfSpeech = trailingSilenceMs >= END_OF_SPEECH_SILENCE_MS
|
|
227
|
+
val windowFull = windowVoicedMs >= WINDOW_VOICED_TARGET_MS
|
|
228
|
+
if (!endOfSpeech && !windowFull) return
|
|
229
|
+
|
|
230
|
+
val startMs = windowStartMs
|
|
231
|
+
val voicedMs = windowVoicedMs
|
|
232
|
+
val window = windowSamples.toFloatArray()
|
|
233
|
+
resetWindow()
|
|
234
|
+
|
|
235
|
+
if (voicedMs < MIN_VOICED_MS) {
|
|
236
|
+
Bridge.log("SpeakerID: window skipped — only ${voicedMs.toInt()}ms voiced (<${MIN_VOICED_MS.toInt()}ms)")
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
identify(window, startMs, nowMs, voicedMs)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private fun identify(window: FloatArray, startMs: Long, endMs: Long, voicedMs: Double) {
|
|
243
|
+
ensureExtractor()
|
|
244
|
+
val extractor = extractor
|
|
245
|
+
if (extractor == null) {
|
|
246
|
+
Bridge.log("SpeakerID: window dropped — extractor not loaded (model missing?)")
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
val currentProfiles = profiles
|
|
250
|
+
if (currentProfiles.isEmpty()) {
|
|
251
|
+
Bridge.log("SpeakerID: window dropped — no enrolled profiles pushed")
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
val stream = extractor.createStream()
|
|
256
|
+
val embedding: FloatArray
|
|
257
|
+
try {
|
|
258
|
+
stream.acceptWaveform(window, SAMPLE_RATE)
|
|
259
|
+
stream.inputFinished()
|
|
260
|
+
if (!extractor.isReady(stream)) {
|
|
261
|
+
Bridge.log("SpeakerID: window dropped — extractor not ready (${window.size} samples)")
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
embedding = l2Normalize(extractor.compute(stream))
|
|
265
|
+
} finally {
|
|
266
|
+
stream.release()
|
|
267
|
+
}
|
|
268
|
+
if (embedding.isEmpty()) {
|
|
269
|
+
Bridge.log("SpeakerID: window dropped — empty embedding")
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
var best: Profile? = null
|
|
274
|
+
var bestCos = -1f
|
|
275
|
+
var second = -1f
|
|
276
|
+
for (profile in currentProfiles) {
|
|
277
|
+
if (profile.embedding.size != embedding.size) continue
|
|
278
|
+
val cos = dot(profile.embedding, embedding)
|
|
279
|
+
if (best == null || cos > bestCos) {
|
|
280
|
+
second = bestCos
|
|
281
|
+
best = profile
|
|
282
|
+
bestCos = cos
|
|
283
|
+
} else if (cos > second) {
|
|
284
|
+
second = cos
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
var matched: Profile? = null
|
|
289
|
+
if (best != null && bestCos >= MATCH_THRESHOLD) {
|
|
290
|
+
// Margin rule: reject ambiguous windows where the runner-up is
|
|
291
|
+
// close (overlapping talkers produce garbage embeddings).
|
|
292
|
+
if (second < 0 || bestCos - second >= MATCH_MARGIN) {
|
|
293
|
+
matched = best
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (matched != null) {
|
|
298
|
+
lastMatch = Match(matched.id, matched.name, bestCos, endMs)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
val durationMs = endMs - startMs
|
|
302
|
+
val speech = "speech=${voicedMs.toInt()}ms window=${durationMs}ms"
|
|
303
|
+
if (matched != null) {
|
|
304
|
+
Bridge.log(
|
|
305
|
+
"SpeakerID: matched ${matched.name} cos=${"%.3f".format(bestCos)} " +
|
|
306
|
+
"second=${"%.3f".format(second)} $speech"
|
|
307
|
+
)
|
|
308
|
+
} else if (best != null) {
|
|
309
|
+
val reason =
|
|
310
|
+
if (bestCos < MATCH_THRESHOLD) "below threshold $MATCH_THRESHOLD"
|
|
311
|
+
else "margin to runner-up < $MATCH_MARGIN"
|
|
312
|
+
Bridge.log(
|
|
313
|
+
"SpeakerID: no match — best ${best.name} cos=${"%.3f".format(bestCos)} " +
|
|
314
|
+
"second=${"%.3f".format(second)} ($reason) $speech"
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
Bridge.sendSpeakerDetected(
|
|
319
|
+
speakerId = matched?.id,
|
|
320
|
+
name = matched?.name,
|
|
321
|
+
similarity = (if (matched != null) bestCos else maxOf(bestCos, 0f)).toDouble(),
|
|
322
|
+
startMs = startMs,
|
|
323
|
+
endMs = endMs,
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// MARK: - Enrollment (executor thread only)
|
|
328
|
+
|
|
329
|
+
private fun processEnrollment(samples: FloatArray, chunkMs: Double, rms: Float, voiced: Boolean) {
|
|
330
|
+
val session = enrollment ?: return
|
|
331
|
+
if (voiced) {
|
|
332
|
+
session.samples.ensureCapacity(session.samples.size + samples.size)
|
|
333
|
+
for (s in samples) session.samples.add(s)
|
|
334
|
+
session.voicedMs += chunkMs
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
val now = System.currentTimeMillis()
|
|
338
|
+
if (now - session.lastProgressAt >= 250) {
|
|
339
|
+
session.lastProgressAt = now
|
|
340
|
+
Bridge.sendSpeakerEnrollProgress(
|
|
341
|
+
voicedSeconds = session.voicedMs / 1000.0,
|
|
342
|
+
targetSeconds = session.targetVoicedMs / 1000.0,
|
|
343
|
+
level = rms.toDouble(),
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (session.voicedMs >= session.targetVoicedMs) {
|
|
348
|
+
enrollment = null
|
|
349
|
+
enrolling.set(false)
|
|
350
|
+
completeEnrollment(session)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private fun completeEnrollment(session: EnrollmentSession) {
|
|
355
|
+
val extractor = extractor
|
|
356
|
+
if (extractor == null) {
|
|
357
|
+
finishEnrollment(ok = false, reason = "model_unavailable", embeddings = emptyList(), source = session.source)
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// One embedding per prompt-sized chunk; internal consistency check
|
|
362
|
+
// catches wind/noise/multiple talkers.
|
|
363
|
+
val chunkSize = session.samples.size / ENROLL_CHUNK_COUNT
|
|
364
|
+
if (chunkSize < SAMPLE_RATE) {
|
|
365
|
+
finishEnrollment(ok = false, reason = "too_short", embeddings = emptyList(), source = session.source)
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
val embeddings = ArrayList<FloatArray>()
|
|
370
|
+
for (i in 0 until ENROLL_CHUNK_COUNT) {
|
|
371
|
+
val start = i * chunkSize
|
|
372
|
+
val end = if (i == ENROLL_CHUNK_COUNT - 1) session.samples.size else start + chunkSize
|
|
373
|
+
val chunk = FloatArray(end - start) { session.samples[start + it] }
|
|
374
|
+
val stream = extractor.createStream()
|
|
375
|
+
try {
|
|
376
|
+
stream.acceptWaveform(chunk, SAMPLE_RATE)
|
|
377
|
+
stream.inputFinished()
|
|
378
|
+
if (!extractor.isReady(stream)) continue
|
|
379
|
+
val embedding = l2Normalize(extractor.compute(stream))
|
|
380
|
+
if (embedding.isNotEmpty()) embeddings.add(embedding)
|
|
381
|
+
} finally {
|
|
382
|
+
stream.release()
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (embeddings.size < 2) {
|
|
387
|
+
finishEnrollment(ok = false, reason = "too_short", embeddings = emptyList(), source = session.source)
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (i in embeddings.indices) {
|
|
392
|
+
for (j in i + 1 until embeddings.size) {
|
|
393
|
+
val cos = dot(embeddings[i], embeddings[j])
|
|
394
|
+
if (cos < ENROLL_CONSISTENCY_THRESHOLD) {
|
|
395
|
+
Bridge.log(
|
|
396
|
+
"SpeakerID: enrollment chunks $i/$j inconsistent — cos=${"%.3f".format(cos)} " +
|
|
397
|
+
"< $ENROLL_CONSISTENCY_THRESHOLD (noise / multiple talkers?)"
|
|
398
|
+
)
|
|
399
|
+
finishEnrollment(ok = false, reason = "inconsistent", embeddings = emptyList(), source = session.source)
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
finishEnrollment(
|
|
406
|
+
ok = true,
|
|
407
|
+
reason = null,
|
|
408
|
+
embeddings = embeddings.map { encodeBase64(it) },
|
|
409
|
+
source = session.source,
|
|
410
|
+
dim = embeddings[0].size,
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private fun finishEnrollment(ok: Boolean, reason: String?, embeddings: List<String>, source: String, dim: Int = 0) {
|
|
415
|
+
enrolling.set(false)
|
|
416
|
+
Bridge.log(
|
|
417
|
+
if (ok) "SpeakerID: enrollment ok — ${embeddings.size} embeddings (dim=$dim, mic=$source)"
|
|
418
|
+
else "SpeakerID: enrollment failed — ${reason ?: "unknown"} (mic=$source)"
|
|
419
|
+
)
|
|
420
|
+
Bridge.sendSpeakerEnrollResult(ok = ok, reason = reason, embeddings = embeddings, dim = dim, source = source)
|
|
421
|
+
val onEnded = enrollmentEnded
|
|
422
|
+
enrollmentEnded = null
|
|
423
|
+
onEnded?.invoke()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// MARK: - Model loading (executor thread only)
|
|
427
|
+
|
|
428
|
+
private fun ensureExtractor() {
|
|
429
|
+
if (extractor != null || extractorLoadAttempted) return
|
|
430
|
+
extractorLoadAttempted = true
|
|
431
|
+
val modelPath = resolvedModelPath()
|
|
432
|
+
if (modelPath == null || !File(modelPath).exists()) {
|
|
433
|
+
Bridge.log("SpeakerID: model not downloaded yet — engine idle")
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
val config = SpeakerEmbeddingExtractorConfig(modelPath, 2, false, "cpu")
|
|
438
|
+
extractor = SpeakerEmbeddingExtractor(null, config)
|
|
439
|
+
Bridge.log("SpeakerID: embedding extractor initialized (dim=${extractor?.dim()})")
|
|
440
|
+
} catch (e: Exception) {
|
|
441
|
+
Bridge.log("SpeakerID: failed to initialize extractor: ${e.message}")
|
|
442
|
+
extractor = null
|
|
443
|
+
} catch (e: LinkageError) {
|
|
444
|
+
Bridge.log("SpeakerID: sherpa-onnx runtime unavailable: ${e.message}")
|
|
445
|
+
extractor = null
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Same Documents-relative reconstruction as the STT model path (the app
|
|
451
|
+
* id changes between dev builds; iOS parity).
|
|
452
|
+
*/
|
|
453
|
+
private fun resolvedModelPath(): String? {
|
|
454
|
+
return try {
|
|
455
|
+
val context = Bridge.getContext()
|
|
456
|
+
val prefs = context.getSharedPreferences(PREFS_NAME, android.content.Context.MODE_PRIVATE)
|
|
457
|
+
val storedPath = prefs.getString(KEY_MODEL_PATH, null) ?: return null
|
|
458
|
+
if (storedPath.contains("/Documents/")) {
|
|
459
|
+
File(context.filesDir, storedPath.substringAfter("/Documents/")).absolutePath
|
|
460
|
+
} else {
|
|
461
|
+
storedPath
|
|
462
|
+
}
|
|
463
|
+
} catch (e: Exception) {
|
|
464
|
+
null
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// MARK: - Helpers
|
|
469
|
+
|
|
470
|
+
private fun resetWindow() {
|
|
471
|
+
windowSamples = ArrayList()
|
|
472
|
+
windowStartMs = 0
|
|
473
|
+
windowVoicedMs = 0.0
|
|
474
|
+
trailingSilenceMs = 0.0
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private fun toFloatArray(pcmData: ByteArray): FloatArray {
|
|
478
|
+
val count = pcmData.size / 2
|
|
479
|
+
val samples = FloatArray(count)
|
|
480
|
+
val buffer = ByteBuffer.wrap(pcmData).order(ByteOrder.LITTLE_ENDIAN)
|
|
481
|
+
for (i in 0 until count) {
|
|
482
|
+
samples[i] = buffer.short / 32768.0f
|
|
483
|
+
}
|
|
484
|
+
return samples
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private fun rms(samples: FloatArray): Float {
|
|
488
|
+
if (samples.isEmpty()) return 0f
|
|
489
|
+
var sum = 0f
|
|
490
|
+
for (s in samples) sum += s * s
|
|
491
|
+
return sqrt(sum / samples.size)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private fun dot(a: FloatArray, b: FloatArray): Float {
|
|
495
|
+
if (a.size != b.size) return 0f
|
|
496
|
+
var sum = 0f
|
|
497
|
+
for (i in a.indices) sum += a[i] * b[i]
|
|
498
|
+
return sum
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private fun l2Normalize(v: FloatArray): FloatArray {
|
|
502
|
+
var sum = 0f
|
|
503
|
+
for (x in v) sum += x * x
|
|
504
|
+
val norm = sqrt(sum)
|
|
505
|
+
if (norm <= 0f) return v
|
|
506
|
+
return FloatArray(v.size) { v[it] / norm }
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private fun encodeBase64(embedding: FloatArray): String {
|
|
510
|
+
val bytes = ByteBuffer.allocate(embedding.size * 4).order(ByteOrder.LITTLE_ENDIAN)
|
|
511
|
+
for (f in embedding) bytes.putFloat(f)
|
|
512
|
+
return Base64.encodeToString(bytes.array(), Base64.NO_WRAP)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
fun decodeEmbedding(base64: String): FloatArray? {
|
|
516
|
+
return try {
|
|
517
|
+
val bytes = Base64.decode(base64, Base64.DEFAULT)
|
|
518
|
+
if (bytes.isEmpty() || bytes.size % 4 != 0) return null
|
|
519
|
+
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
|
|
520
|
+
FloatArray(bytes.size / 4) { buffer.getFloat(it * 4) }
|
|
521
|
+
} catch (e: IllegalArgumentException) {
|
|
522
|
+
null
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|