Repository: TeamOpenSmartGlasses/SmartGlassesManager Branch: master Commit: ec907f8aa144 Files: 396 Total size: 1.5 MB Directory structure: gitextract_hmb1u4o6/ ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── SGM_android/ │ ├── .gitignore │ ├── README.md │ ├── SmartGlassesManager/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ ├── src/ │ │ │ ├── androidTest/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── teamopensmartglasses/ │ │ │ │ └── smartglassesmanager/ │ │ │ │ └── ExampleInstrumentedTest.java │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── cpp/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── jni/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ ├── ogg_opus_encoder.cc │ │ │ │ │ │ └── ogg_opus_encoder.h │ │ │ │ │ ├── libogg/ │ │ │ │ │ │ ├── Makefile.am │ │ │ │ │ │ ├── config_types.h │ │ │ │ │ │ ├── config_types.h.in │ │ │ │ │ │ ├── ogg.h │ │ │ │ │ │ └── os_types.h │ │ │ │ │ ├── libopus/ │ │ │ │ │ │ ├── opus.h │ │ │ │ │ │ ├── opus_custom.h │ │ │ │ │ │ ├── opus_defines.h │ │ │ │ │ │ ├── opus_multistream.h │ │ │ │ │ │ ├── opus_projection.h │ │ │ │ │ │ └── opus_types.h │ │ │ │ │ ├── ogg_opus_encoder.cc │ │ │ │ │ ├── ogg_opus_encoder.h │ │ │ │ │ └── opus_tools/ │ │ │ │ │ └── opus_header.h │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── teamopensmartglasses/ │ │ │ │ │ └── smartglassesmanager/ │ │ │ │ │ ├── SmartGlassesAndroidService.java │ │ │ │ │ ├── SmartGlassesRepresentative.java │ │ │ │ │ ├── comms/ │ │ │ │ │ │ ├── AspWebsocketServer.java │ │ │ │ │ │ ├── AudioSystem.java │ │ │ │ │ │ ├── MessageTypes.java │ │ │ │ │ │ └── VolleyCallback.java │ │ │ │ │ ├── eventbusmessages/ │ │ │ │ │ │ ├── AudioChunkNewEvent.java │ │ │ │ │ │ ├── DisableBleScoAudioEvent.java │ │ │ │ │ │ ├── GoogleAudioChunkNewEvent.java │ │ │ │ │ │ ├── PauseAsrEvent.java │ │ │ │ │ │ ├── PostGenericGlobalMessageEvent.java │ │ │ │ │ │ ├── ScoStartEvent.java │ │ │ │ │ │ ├── SetFontSizeEvent.java │ │ │ │ │ │ ├── SmartGlassesConnectedEvent.java │ │ │ │ │ │ ├── SmartGlassesConnectionEvent.java │ │ │ │ │ │ ├── TextToSpeechEvent.java │ │ │ │ │ │ └── VoskAudioChunkNewEvent.java │ │ │ │ │ ├── hci/ │ │ │ │ │ │ ├── AudioChunkCallback.java │ │ │ │ │ │ ├── HearItBleMicrophone.java │ │ │ │ │ │ └── MicrophoneLocalAndBluetooth.java │ │ │ │ │ ├── smartglassescommunicators/ │ │ │ │ │ │ ├── AndroidSGC.java │ │ │ │ │ │ ├── AudioWearableSGC.java │ │ │ │ │ │ ├── SmartGlassesCommunicator.java │ │ │ │ │ │ ├── SmartGlassesFontSize.java │ │ │ │ │ │ ├── SmartGlassesModes.java │ │ │ │ │ │ ├── TextLineSG.java │ │ │ │ │ │ ├── UltraliteLayoutHelper.java │ │ │ │ │ │ └── UltraliteSGC.java │ │ │ │ │ ├── speechrecognition/ │ │ │ │ │ │ ├── ASR_FRAMEWORKS.java │ │ │ │ │ │ ├── SpeechRecFramework.java │ │ │ │ │ │ ├── SpeechRecSwitchSystem.java │ │ │ │ │ │ ├── azure/ │ │ │ │ │ │ │ ├── AzureAudioInputStream.java │ │ │ │ │ │ │ └── SpeechRecAzure.java │ │ │ │ │ │ ├── deepgram/ │ │ │ │ │ │ │ └── SpeechRecDeepgram.java │ │ │ │ │ │ ├── google/ │ │ │ │ │ │ │ ├── SpeechRecGoogle.java │ │ │ │ │ │ │ ├── asr/ │ │ │ │ │ │ │ │ ├── AlwaysSameSpeakerIDLabeler.java │ │ │ │ │ │ │ │ ├── AlwaysSpeechPolicy.java │ │ │ │ │ │ │ │ ├── RepeatingRecognitionSession.java │ │ │ │ │ │ │ │ ├── RequestForRecognitionThread.java │ │ │ │ │ │ │ │ ├── SafeTranscriptionResultFormatter.java │ │ │ │ │ │ │ │ ├── SpeechDetectionPolicy.java │ │ │ │ │ │ │ │ ├── SpeechSession.java │ │ │ │ │ │ │ │ ├── SpeechSessionFactory.java │ │ │ │ │ │ │ │ ├── SpeechSessionListener.java │ │ │ │ │ │ │ │ ├── TimeUtil.java │ │ │ │ │ │ │ │ ├── TranscriptionErrorPublisher.java │ │ │ │ │ │ │ │ ├── TranscriptionResultFormatter.java │ │ │ │ │ │ │ │ ├── TranscriptionResultReceiver.java │ │ │ │ │ │ │ │ ├── TranscriptionResultUpdatePublisher.java │ │ │ │ │ │ │ │ └── asrhelpers/ │ │ │ │ │ │ │ │ ├── CircularByteBuffer.java │ │ │ │ │ │ │ │ ├── NetworkConnectionChecker.java │ │ │ │ │ │ │ │ ├── ResponseTextUiAdapter.java │ │ │ │ │ │ │ │ ├── SampleProcessorInterface.java │ │ │ │ │ │ │ │ ├── SpeakerIDLabeler.java │ │ │ │ │ │ │ │ └── StreamingAudioEncoder.java │ │ │ │ │ │ │ └── gcloudspeech/ │ │ │ │ │ │ │ ├── CloudSpeechSession.java │ │ │ │ │ │ │ ├── CloudSpeechSessionFactory.java │ │ │ │ │ │ │ ├── CloudSpeechStreamObserver.java │ │ │ │ │ │ │ └── TimestampCalculator.java │ │ │ │ │ │ ├── vad/ │ │ │ │ │ │ │ └── VadGateSpeechPolicy.java │ │ │ │ │ │ └── vosk/ │ │ │ │ │ │ └── SpeechRecVosk.java │ │ │ │ │ ├── supportedglasses/ │ │ │ │ │ │ ├── AndroidPhoneTestGlasses.java │ │ │ │ │ │ ├── AudioWearable.java │ │ │ │ │ │ ├── EngoTwo.java │ │ │ │ │ │ ├── EvenRealitiesG1.java │ │ │ │ │ │ ├── InmoAirOne.java │ │ │ │ │ │ ├── InmoGo.java │ │ │ │ │ │ ├── SmartGlassesDevice.java │ │ │ │ │ │ ├── SmartGlassesOperatingSystem.java │ │ │ │ │ │ ├── TCLRayNeoXTwo.java │ │ │ │ │ │ ├── VuzixShield.java │ │ │ │ │ │ └── VuzixUltralite.java │ │ │ │ │ ├── texttospeech/ │ │ │ │ │ │ └── TextToSpeechSystem.java │ │ │ │ │ └── utils/ │ │ │ │ │ ├── AES.java │ │ │ │ │ ├── BitmapJavaUtils.java │ │ │ │ │ ├── NetworkUtils.java │ │ │ │ │ └── PermissionsUtils.java │ │ │ │ ├── proto/ │ │ │ │ │ └── com/ │ │ │ │ │ └── google/ │ │ │ │ │ └── audio/ │ │ │ │ │ ├── asr/ │ │ │ │ │ │ ├── cloud_speech_session_params.proto │ │ │ │ │ │ ├── cloud_speech_stream_observer_params.proto │ │ │ │ │ │ ├── speech_recognition_model_options.proto │ │ │ │ │ │ ├── transcription_result.proto │ │ │ │ │ │ └── transcription_result_formatter_options.proto │ │ │ │ │ ├── codec_and_bitrate.proto │ │ │ │ │ ├── network_state.proto │ │ │ │ │ └── speaker_id_info.proto │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ ├── outline_device_hub_24.xml │ │ │ │ │ ├── outline_groups_24.xml │ │ │ │ │ ├── outline_memory_24.xml │ │ │ │ │ ├── outline_settings_24.xml │ │ │ │ │ ├── pretty_thin_border.xml │ │ │ │ │ ├── rounded_corner.xml │ │ │ │ │ ├── rounded_corners_bg.xml │ │ │ │ │ ├── rounded_shadow_bg.xml │ │ │ │ │ ├── sgm_launcher_background.xml │ │ │ │ │ ├── textview_background.xml │ │ │ │ │ ├── wis_launcher_background.xml │ │ │ │ │ └── wis_launcher_transparent_background.xml │ │ │ │ ├── drawable-v24/ │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── layout/ │ │ │ │ │ ├── activity_main.xml │ │ │ │ │ ├── all_transcripts_fragment.xml │ │ │ │ │ ├── api_key_message.xml │ │ │ │ │ ├── connecting_to_glasses_fragment.xml │ │ │ │ │ ├── default_app_dialog.xml │ │ │ │ │ ├── export_data_fragment.xml │ │ │ │ │ ├── glasses_connected_fragment.xml │ │ │ │ │ ├── image_item.xml │ │ │ │ │ ├── main_ui_fragment.xml │ │ │ │ │ ├── menu_item_exposed_dropdown.xml │ │ │ │ │ ├── phrase_context_fragment.xml │ │ │ │ │ ├── phrase_recyclerview_item.xml │ │ │ │ │ ├── reference_card_recyclerview_item.xml │ │ │ │ │ ├── response_text_box.xml │ │ │ │ │ ├── select_glasses_fragment.xml │ │ │ │ │ ├── settings_fragment.xml │ │ │ │ │ ├── smart_glasses_debug_fragment.xml │ │ │ │ │ └── smart_glasses_list_item.xml │ │ │ │ ├── menu/ │ │ │ │ │ ├── bottom_nav_main_menu.xml │ │ │ │ │ └── memory_cache_menu.xml │ │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ ├── ic_launcher_round.xml │ │ │ │ │ ├── sgm_launcher.xml │ │ │ │ │ ├── sgm_launcher_round.xml │ │ │ │ │ ├── wis_launcher.xml │ │ │ │ │ ├── wis_launcher_round.xml │ │ │ │ │ ├── wis_launcher_transparent.xml │ │ │ │ │ └── wis_launcher_transparent_round.xml │ │ │ │ ├── navigation/ │ │ │ │ │ └── nav_graph.xml │ │ │ │ ├── values/ │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── dimens.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ └── xml/ │ │ │ │ └── file_provider.xml │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── teamopensmartglasses/ │ │ │ └── smartglassesmanager/ │ │ │ └── ExampleUnitTest.java │ │ └── third_party/ │ │ ├── CMakeLists.txt │ │ ├── CMakeLists_libogg.txt │ │ ├── CMakeLists_libopus.txt │ │ ├── CMakeLists_opus-tools.txt │ │ ├── libogg/ │ │ │ └── lib/ │ │ │ ├── arm64-v8a/ │ │ │ │ └── libogg.a │ │ │ ├── armeabi-v7a/ │ │ │ │ └── libogg.a │ │ │ ├── x86/ │ │ │ │ └── libogg.a │ │ │ └── x86_64/ │ │ │ └── libogg.a │ │ ├── libopus/ │ │ │ └── lib/ │ │ │ ├── arm64-v8a/ │ │ │ │ └── libopus.a │ │ │ ├── armeabi-v7a/ │ │ │ │ └── libopus.a │ │ │ ├── x86/ │ │ │ │ └── libopus.a │ │ │ └── x86_64/ │ │ │ └── libopus.a │ │ └── opus_tools/ │ │ ├── lib/ │ │ │ ├── arm64-v8a/ │ │ │ │ └── libopus_header.a │ │ │ ├── armeabi-v7a/ │ │ │ │ └── libopus_header.a │ │ │ ├── x86/ │ │ │ │ └── libopus_header.a │ │ │ └── x86_64/ │ │ │ └── libopus_header.a │ │ └── src/ │ │ ├── .gitignore │ │ ├── AUTHORS │ │ ├── CMakeLists.txt │ │ ├── COPYING │ │ ├── ChangeLog │ │ ├── Makefile.am │ │ ├── Makefile.unix │ │ ├── NEWS │ │ ├── README │ │ ├── autogen.sh │ │ ├── configure.ac │ │ ├── man/ │ │ │ ├── opusdec.1 │ │ │ ├── opusenc.1 │ │ │ └── opusinfo.1 │ │ └── src/ │ │ ├── arch.h │ │ ├── audio-in.c │ │ ├── diag_range.c │ │ ├── diag_range.h │ │ ├── info_opus.c │ │ ├── info_opus.h │ │ ├── lpc.c │ │ ├── lpc.h │ │ ├── opus_header.c │ │ ├── opus_header.h │ │ ├── opusdec.c │ │ ├── opusenc.c │ │ ├── opusenc.h │ │ ├── opusinfo.c │ │ ├── opusinfo.h │ │ ├── os_support.h │ │ ├── resample.c │ │ ├── speex_resampler.h │ │ ├── stack_alloc.h │ │ ├── wav_io.c │ │ ├── wav_io.h │ │ ├── wave_out.c │ │ └── wave_out.h │ ├── build.gradle │ ├── build_all.sh │ ├── build_log.txt │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── SGM_iOS/ │ └── SGM/ │ ├── .gitignore │ ├── .swiftpm/ │ │ └── xcode/ │ │ └── package.xcworkspace/ │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── Package.resolved │ ├── Package.swift │ ├── Sources/ │ │ └── SGM/ │ │ └── SGM.swift │ └── Tests/ │ └── SGMTests/ │ └── SGMTests.swift ├── android_smart_glasses_frontend/ │ ├── README.md │ └── main/ │ ├── .gitignore │ ├── app/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── libs/ │ │ │ └── hiddencamera-release.aar │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── wearableintelligencesystemandroidsmartglasses/ │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets/ │ │ │ │ └── autocite_references.csv │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── wearableintelligencesystem/ │ │ │ │ └── androidsmartglasses/ │ │ │ │ ├── ASPClientSocket.java │ │ │ │ ├── AudioSystem.java │ │ │ │ ├── ImageAdapter.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── WearableAiService.java │ │ │ │ ├── archive/ │ │ │ │ │ ├── AudioService.java │ │ │ │ │ ├── GlboxClientSocket.java │ │ │ │ │ └── SendImage.java │ │ │ │ ├── comms/ │ │ │ │ │ ├── AsgWebSocketClient.java │ │ │ │ │ ├── MessageTypes.java │ │ │ │ │ ├── WebSocketManager.java │ │ │ │ │ ├── WifiStatusCallback.java │ │ │ │ │ └── WifiUtils.java │ │ │ │ ├── sensors/ │ │ │ │ │ ├── AudioChunkCallback.java │ │ │ │ │ ├── BluetoothMic.java │ │ │ │ │ ├── BluetoothScanner.java │ │ │ │ │ ├── Muse.java │ │ │ │ │ └── MuseService.java │ │ │ │ ├── ui/ │ │ │ │ │ ├── ASGFragment.java │ │ │ │ │ ├── CommandPostUi.java │ │ │ │ │ ├── CommandResolveUi.java │ │ │ │ │ ├── ConvoModeUi.java │ │ │ │ │ ├── HomePromptUi.java │ │ │ │ │ ├── LanguageTranslateUi.java │ │ │ │ │ ├── LiveLifeCaptionsUi.java │ │ │ │ │ ├── ObjectTranslateUi.java │ │ │ │ │ ├── ReferenceUi.java │ │ │ │ │ ├── RequiredArgsUi.java │ │ │ │ │ ├── SelectableImageGridUi.java │ │ │ │ │ ├── TextWallUi.java │ │ │ │ │ ├── UiUtils.java │ │ │ │ │ ├── WakeWordPostUi.java │ │ │ │ │ └── adapters/ │ │ │ │ │ └── CommandListRecyclerViewAdapter.java │ │ │ │ └── utils/ │ │ │ │ └── AES.java │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ ├── circle_shape.xml │ │ │ │ ├── ic_command_failed.xml │ │ │ │ ├── ic_command_success.xml │ │ │ │ ├── ic_full_battery.xml │ │ │ │ ├── ic_full_battery_charging.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── ic_low_battery.xml │ │ │ │ ├── ic_low_battery_charging.xml │ │ │ │ ├── ic_mic_listening.xml │ │ │ │ ├── ic_phone_connected.xml │ │ │ │ ├── ic_phone_disconnected.xml │ │ │ │ ├── ic_wifi_off.xml │ │ │ │ ├── ic_wifi_on.xml │ │ │ │ ├── selector_image_gridview.xml │ │ │ │ └── wis_launcher_background.xml │ │ │ ├── drawable-v24/ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── layout/ │ │ │ │ ├── activity_main.xml │ │ │ │ ├── blank_screen.xml │ │ │ │ ├── command_list_recyclerview_item.xml │ │ │ │ ├── command_post_fragment.xml │ │ │ │ ├── command_resolve_fragment.xml │ │ │ │ ├── convo_mode_fragment.xml │ │ │ │ ├── home_prompt_fragment.xml │ │ │ │ ├── image_gridview.xml │ │ │ │ ├── live_life_caption_text.xml │ │ │ │ ├── object_translate_mode_view.xml │ │ │ │ ├── old_main.xml │ │ │ │ ├── reference_card.xml │ │ │ │ ├── required_args_fragment.xml │ │ │ │ ├── social_intelligence_activity.xml │ │ │ │ ├── text_block.xml │ │ │ │ ├── text_list.xml │ │ │ │ ├── text_wall_card.xml │ │ │ │ ├── translate_mode_view.xml │ │ │ │ ├── viewfinder.xml │ │ │ │ ├── wake_word_post_fragment.xml │ │ │ │ └── wearable_face_recognizer.xml │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ ├── ic_launcher.xml │ │ │ │ ├── ic_launcher_round.xml │ │ │ │ ├── wis_launcher.xml │ │ │ │ └── wis_launcher_round.xml │ │ │ ├── navigation/ │ │ │ │ └── nav_graph.xml │ │ │ ├── values/ │ │ │ │ ├── colors.xml │ │ │ │ ├── dimensions.xml │ │ │ │ ├── key.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ └── values-night/ │ │ │ └── themes.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── wearableintelligencesystemandroidsmartglasses/ │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle └── example_smart_glasses_app/ ├── .gitignore ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── teamopensmartglasses/ │ │ └── example_smart_glasses_app/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── teamopensmartglasses/ │ │ │ └── example_smart_glasses_app/ │ │ │ ├── MainActivity.java │ │ │ ├── SmartGlassesService.java │ │ │ └── ui/ │ │ │ ├── ConnectingToSmartGlassesUi.java │ │ │ ├── GlassesConnectedFragment.java │ │ │ ├── SelectSmartGlassesUi.java │ │ │ ├── SettingsUi.java │ │ │ ├── SmartGlassesListAdapter.java │ │ │ └── UiUtils.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── ic_launcher_background.xml │ │ │ ├── outline_device_hub_24.xml │ │ │ ├── outline_groups_24.xml │ │ │ ├── outline_memory_24.xml │ │ │ ├── outline_settings_24.xml │ │ │ ├── pretty_thin_border.xml │ │ │ ├── rounded_corner.xml │ │ │ ├── rounded_corners_bg.xml │ │ │ ├── rounded_shadow_bg.xml │ │ │ ├── sgm_launcher_background.xml │ │ │ ├── textview_background.xml │ │ │ ├── wis_launcher_background.xml │ │ │ └── wis_launcher_transparent_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── all_transcripts_fragment.xml │ │ │ ├── api_key_message.xml │ │ │ ├── connecting_to_glasses_fragment.xml │ │ │ ├── default_app_dialog.xml │ │ │ ├── export_data_fragment.xml │ │ │ ├── glasses_connected_fragment.xml │ │ │ ├── image_item.xml │ │ │ ├── main_ui_fragment.xml │ │ │ ├── menu_item_exposed_dropdown.xml │ │ │ ├── phrase_context_fragment.xml │ │ │ ├── phrase_recyclerview_item.xml │ │ │ ├── reference_card_recyclerview_item.xml │ │ │ ├── response_text_box.xml │ │ │ ├── select_glasses_fragment.xml │ │ │ ├── settings_fragment.xml │ │ │ ├── smart_glasses_debug_fragment.xml │ │ │ └── smart_glasses_list_item.xml │ │ ├── menu/ │ │ │ ├── bottom_nav_main_menu.xml │ │ │ └── memory_cache_menu.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── navigation/ │ │ │ └── nav_graph.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── xml/ │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test/ │ └── java/ │ └── com/ │ └── teamopensmartglasses/ │ └── example_smart_glasses_app/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ __pycache__ *.pyc azure_key.txt # data *.jpg *mp3 *mp4 # data/ # models models/ *.pkl *.h5 *.scorer #*.tflite *.pbmm *.pth *.pt *.tar #other # data/ #ANDROID # Built application files *.apk #*.aar *.ap_ *.aab # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Uncomment the following line in case you need and you don't have the release build type files in your app # release/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # IntelliJ *.iml .idea/workspace.xml .idea/misc.xml .idea/tasks.xml .idea/gradle.xml .idea/assetWizardSettings.xml .idea/dictionaries .idea/libraries # IntelliJ *.iml .idea/ misc.xml deploymentTargetDropDown.xml render.experimental.xml # Android Studio 3 in .gitignore file. .idea/caches .idea/modules.xml # Comment next line if keeping position of elements in Navigation Editor is relevant for you .idea/navEditor.xml # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. #*.jks #*.keystore # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild .cxx/ # Google Services (e.g. APIs or Firebase) # google-services.json # Freeline freeline.py freeline/ freeline_project_description.json # fastlane fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output fastlane/readme.md # Version control vcs.xml # lint lint/intermediates/ lint/generated/ lint/outputs/ lint/tmp/ # lint/reports/ # Android Profiling *.hprof .classpath .project .settings keys/ ================================================ FILE: .gitmodules ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Cayden Pierce Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # SmartGlassesManager ### Update 2024 - **SmartGlassesManager** is now part of [AugmentOS](https://augmentos.org/), a unifying OS for smart glasses such as *Even Reality G1* and *Vuzix Z100*. It combines your favorite smart glasses apps, an app store, and SDK. We hope you like it <3

## Why Use The Smart Glasses Manager? ### Developers Building apps that run on a smart phone and stream data to smart glasses is hard, but it’s how today’s lightweight smart glasses work. We handle the connection, UI, data streaming, and transcription, allowing you to rapidly develop smart glasses applications that run on any pair of smart glasses. ### Industry Your smart glasses won’t see massive consumer adoption if third party developers can’t make awesome apps for your hardware. The SmartGlassesManager is a middleware which already supports many apps and makes it easy for developers to build more. That means, if you add support to your hardware for a single application – the SmartGlassesManager – your glasses will instantly support a plethora of consumer-facing applications.

## Features 1. Connect smart phone to smart glasses, auto-reconnect - Wifi, Bluetooth, Android, MCU glasses all supported 2. Receive audio + sensors from glasses 3. Transcribe audio 4. Abstracted interface to show info on the glasses ## Fork of the Wearable Intelligence System This repo is a fork of the [Wearable Intelligence System](https://github.com/emexlabs/WearableIntelligenceSystem). The Wearable Intelligence System was started at [Emex Labs](https://emexwearables.com) by [Cayden Pierce](https://caydenpierce.com/). This repo has a lot of the history cleaned to make it easier to manage, see the WIS repo for full history. ## Install / Use You will need two pieces of hardware to run the system: 1. ASP - Android Smart Phone running Android 12+ 2. A pair of smart glasses: - Vuzix Z100 / Ultralite OEM Reference Platform - Vuzix Shield - Activelook Engo 1 or Activelook Engo 2 - Inmo Air 1 or Inmo Air 2 - Vuzix Blade 2 - TCL RayNeo X2 ### Install 1. On your Android smart phone, flash the ASP app from Android Studio, the releases page, or from Google Play. - Play Store: Coming soon - [Github latest release](https://github.com/TeamOpenSmartGlasses/SmartGlassesManager/releases) 2. If using Android Smart Glasses, then on your smart glasses, download or flash the ASG client app: - [Github latest release](https://github.com/TeamOpenSmartGlasses/SmartGlassesManager/releases) 3. * Launch the "Smart Glasses Manager" app on your smart phone * Accept permissions. * If using Android Smart Glasses -> Tap "Start Wifi Hotspot", turn on (configure password if necessary) your wifi hotspot, then go "Back" to return - Connect smart glasses WiFi to the smart phone WiFi hotspot - Enable mobile data (or wifi sharing) on Android smart phone - Start ASG application on smart glasses - The phone connection icon will be green if the glasses are connected to your phone. If you speak, you'll see a live transcript on the smart glasses screen. * If using Bluetooth Smart Glasses -> Turn on Bluetooth on phone. 3. Tap "Connect Smart Glasses" and choose your glasses. 4. Setup complete. ### Normal Use Here's how to launch the system after you've already done the initial setup above: 1. Launch "Smart Glasses Manager" app on smart phone 2. Enable mobile hotspot on smart phone with the "Start WiFi Hotspot" button 3. Connect Android smart glasses to Android smart phone WiFi hotspot. 4. Launch "WIS" app on smart glasses. 5. Verify system is running by the "Smart Glasses Conection Indicator" icon turning white on the smart glasses HUD. ## Documentation / Developers (WIP) Here are the docs: https://github.com/TeamOpenSmartGlasses/SmartGlassesManager/wiki ## Authors The system is fully open source and built by [TeamOpenSmartGlasses](https://teamopensmartglasses.com). We're a team building open source smart glasses technology to enhance user's intelligence in daily life. Join us: https://discord.gg/5ukNvkEAqT The TeamOpenSmartGlasses members who are contributing to this project include: - [Cayden Pierce](https://caydenpierce.com) - [Alex Israelov](http://www.alexisraelov.com/) We are building a community that is building cognitive augmentation technologies together. ================================================ FILE: SGM_android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/caches /.idea/libraries /.idea/modules.xml /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild .cxx local.properties ================================================ FILE: SGM_android/README.md ================================================ ### Special instructions if you need to mess with the OGG streaming CPP JNI. 0. Run Linux (as you should be). 1. Install Java 17. 2. Ensure Java 17 is default Java (can set with `sudo update-java-alternatives`). 3. Run `chmod 777 ./gradle/` and `chmod 777 ./gradle/`. 4. Set your ANDROID_SDK_PATH WITH `export $ANDROID_SDK_PATH=`. 5. Go into android folder and run `bash build_all.sh` to build everything. 6. If you get gradle version issues, install gradle 8.0.2: https://linuxhint.com/installing_gradle_ubuntu/ (follow the instructions, but replace 7.4.2 with 8.0.2). 7. Subsequent builds, you can just run `./gradlew assembleDebug --stacktrace` to build the APK. 8. Now that CPP/JNI is built, you can use Android Studio to edit app and install APK on phone (located in app/build/outputs/debug/). If you change anything related to CPP/JNI code, you may have to rebuild. ================================================ FILE: SGM_android/SmartGlassesManager/.gitignore ================================================ /build ================================================ FILE: SGM_android/SmartGlassesManager/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.22.1) project("some-name-needed-here") add_subdirectory(src/main/cpp) add_subdirectory(src/main/cpp/jni) ================================================ FILE: SGM_android/SmartGlassesManager/build.gradle ================================================ plugins { id 'com.android.library' // id 'kotlin-android' id 'com.google.protobuf' version '0.9.2' } ext { kotlin_version = '1.6.21' roomVersion = '2.2.3' archLifecycleVersion = '2.2.0-rc03' coreTestingVersion = '2.1.0' kotlinVersion = '1.6.21' } android { namespace "com.teamopensmartglasses.smartglassesmanager" compileSdk 33 // aaptOptions { // noCompress "tflite" // } packagingOptions { exclude 'META-INF/proguard/androidx-annotations.pro' resources { excludes += '/META-INF/{AL2.0,LGPL2.1,DEPENDENCIES}' excludes += '/META-INF/INDEX.LIST' } } defaultConfig { minSdk 30 targetSdk 32 versionCode 3 versionName "0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // ndk { // abiFilters 'armeabi-v7a', 'arm64-v8a' // } } externalNativeBuild { cmake { '-DANDROID_STL=c++_static' } } // buildTypes { // release { // minifyEnabled false // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // } //// debug { //// applicationIdSuffix ".debug" //so we can have both release and debug on the same phone //// } // } // compileOptions { // sourceCompatibility JavaVersion.VERSION_1_8 // targetCompatibility JavaVersion.VERSION_1_8 // } // // sourceSets { // main { // assets.srcDirs += "$buildDir/generated/assets" // java.srcDirs += 'src/main/java' // proto.srcDirs += 'src/main/proto' // } // } // // externalNativeBuild { // cmake { // path file('CMakeLists.txt') // version '3.22.1' // } // } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation 'androidx.navigation:navigation-fragment:2.2.0' implementation 'androidx.navigation:navigation-ui:2.2.0' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' implementation 'com.google.protobuf:protobuf-java:3.21.12' implementation 'com.google.protobuf:protobuf-java-util:3.21.12' implementation 'com.google.api.grpc:grpc-google-cloud-speech-v1p1beta1:2.6.0' implementation "androidx.concurrent:concurrent-futures:1.0.0-alpha03" implementation "androidx.lifecycle:lifecycle-common:2.2.0" implementation 'androidx.preference:preference:1.2.0' implementation 'joda-time:joda-time:2.12.2' implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.appcompat:appcompat:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.1.0" implementation "androidx.core:core:1.1.0-rc03" "androidx.legacy:legacy-support-v4:1.0.0" implementation 'io.grpc:grpc-okhttp:1.53.0' implementation "androidx.recyclerview:recyclerview:1.1.0-beta02" implementation "com.github.bumptech.glide:glide:4.11.0" implementation "com.google.android.material:material:1.6.0-alpha01" implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "com.google.flogger:flogger-system-backend:0.3.1" implementation "com.google.flogger:flogger:0.3.1" implementation "com.google.guava:listenablefuture:1.0" implementation "junit:junit:4.12" implementation "org.hamcrest:hamcrest-library:1.3" implementation 'com.squareup.picasso:picasso:2.8' implementation "org.java-websocket:Java-WebSocket:1.5.2" implementation "io.reactivex.rxjava3:rxandroid:3.0.0" implementation "io.reactivex.rxjava3:rxjava:3.0.0" implementation "com.google.android.gms:play-services-tasks:18.0.0" implementation "com.google.android.gms:play-services-location:18.0.0" implementation "com.google.code.gson:gson:2.8.6" //ROOM // Room components implementation "androidx.room:room-runtime:$roomVersion" annotationProcessor "androidx.room:room-compiler:$roomVersion" androidTestImplementation "androidx.room:room-testing:$roomVersion" // Lifecycle components implementation "androidx.lifecycle:lifecycle-extensions:$archLifecycleVersion" annotationProcessor "androidx.lifecycle:lifecycle-compiler:$archLifecycleVersion" implementation "com.google.guava:guava:28.1-android" //VOSK ASR implementation 'net.java.dev.jna:jna:5.8.0@aar' implementation "com.alphacephei:vosk-android:0.3.34@aar" //java.util.time backport implementation "org.threeten:threetenbp:1.5.2" //http(s) lib implementation "com.android.volley:volley:1.2.1" //EventBus, greenrobot implementation("org.greenrobot:eventbus:3.3.1") //activelook SDK //implementation 'com.github.activelook:android-sdk:v4.4.0' //Json (de/)serialization implementation 'com.google.code.gson:gson:2.10.1' //for VAD (using Silero DNN) implementation 'com.github.gkonovalov:android-vad:2.0.1' implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.15.0' //Vuzix Ultralite OEM Platform / Vuzix Z100 implementation 'com.vuzix:ultralite-sdk-android:1.4.1' //Azure ASR implementation "com.microsoft.cognitiveservices.speech:client-sdk:1.38.0" implementation project(path: ':AugmentOSLib') } //VOSK ASR prep models - need one for each language tasks.register('genUUID_en') { def uuid = UUID.randomUUID().toString() def odir = file("$buildDir/generated/assets/model-en-us") def ofile = file("$odir/uuid") doLast { mkdir odir ofile.text = uuid } } tasks.register('genUUID_fr') { def uuid = UUID.randomUUID().toString() def odir = file("$buildDir/generated/assets/model-fr-small") def ofile = file("$odir/uuid") doLast { mkdir odir ofile.text = uuid } } preBuild.dependsOn(genUUID_en) preBuild.dependsOn(genUUID_fr) protobuf { protoc { // use this compiler, from the maven repo (instead of a local file, for instance) artifact = 'com.google.protobuf:protoc:3.21.12' } generateProtoTasks { all().configureEach { task -> // remove the default java output task.builtins { remove java } // add the java output with lite runtime task.builtins { java { option 'lite' } } } } } ================================================ FILE: SGM_android/SmartGlassesManager/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile #don't leave logs in release build: -dontskipnonpubliclibraryclasses -dontobfuscate -forceprocessing -optimizationpasses 5 -keep class * extends android.app.Activity -assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); } ================================================ FILE: SGM_android/SmartGlassesManager/src/androidTest/java/com/teamopensmartglasses/smartglassesmanager/ExampleInstrumentedTest.java ================================================ package com.teamopensmartglasses.smartglassesmanager; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); assertEquals("com.wearableintelligencesystem.androidsmartphone", appContext.getPackageName()); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/AndroidManifest.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/CMakeLists.txt ================================================ # Builds the ogg and opus encoder library which used third party module. cmake_minimum_required(VERSION 3.22.1) # set related path of third party libraries. set(third_party_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../third_party) # libogg library refer from https://github.com/xiph/ogg. set(libogg_INCLUDE ${third_party_DIR}/libogg/include) set(libogg_LIB ${third_party_DIR}/libogg/lib/${ANDROID_ABI}) add_library(lib_ogg STATIC IMPORTED) set_target_properties(lib_ogg PROPERTIES IMPORTED_LOCATION ${libogg_LIB}/libogg.a) # libopus library refer from https://github.com/xiph/opus. set(libopus_INCLUDE ${third_party_DIR}/libopus/include) set(libopus_LIB ${third_party_DIR}/libopus/lib/${ANDROID_ABI}) add_library(lib_opus STATIC IMPORTED) set_target_properties(lib_opus PROPERTIES IMPORTED_LOCATION ${libopus_LIB}/libopus.a) # opus-tools https://github.com/xiph/opus-tools. set(opus_tools_INCLUDE ${third_party_DIR}/opus_tools/include) set(opus_tools_LIB ${third_party_DIR}/opus_tools/lib/${ANDROID_ABI}) add_library(lib_opus_header STATIC IMPORTED) set_target_properties(lib_opus_header PROPERTIES IMPORTED_LOCATION ${opus_tools_LIB}/libopus_header.a) # Build ogg_opus_encoder_tool shared lib. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") add_library(ogg_opus_encoder_tool SHARED ogg_opus_encoder.cc) target_include_directories(ogg_opus_encoder_tool PRIVATE ${libogg_INCLUDE}) target_link_libraries(ogg_opus_encoder_tool lib_opus lib_ogg lib_opus_header) ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/jni/CMakeLists.txt ================================================ #build for ogg_opus_encoder shared library cmake_minimum_required(VERSION 3.22.1) add_library(ogg_opus_encoder SHARED ogg_opus_encoder.cc ../ogg_opus_encoder.cc) # Include libraries needed for ogg_opus_encoder target_link_libraries(ogg_opus_encoder ogg_opus_encoder_tool) ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/jni/ogg_opus_encoder.cc ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ogg_opus_encoder.h" #include #include #include #include "../ogg_opus_encoder.h" namespace { using audio_util::OggOpusEncoder; OggOpusEncoder* GetInstanceOrDie(jlong ptr) { assert(ptr); return reinterpret_cast(ptr); } bool VerifyInitialized(const std::string& function_name, jlong instance_ptr) { if (instance_ptr == 0) { fprintf(stderr, "%s called prior to allocate() or after destroy()!\n", function_name.c_str()); return false; } OggOpusEncoder* instance = GetInstanceOrDie(instance_ptr); if (instance == nullptr) { fprintf(stderr, "%s called prior to init()!\n", function_name.c_str()); return false; } return true; } JNIEXPORT jbyteArray convertToByteArray(const std::vector& data, JNIEnv* env) { jbyteArray byteArray = env->NewByteArray(data.size()); env->SetByteArrayRegion(byteArray, 0, data.size(), reinterpret_cast(data.data())); return byteArray; } } // namespace JNIEXPORT jlong JNICALL JNI_METHOD(init)(JNIEnv* env, jobject instance, jint num_channels, jint bitrate_bits_per_second, jint sample_rate_hz, jboolean use_vbr) { constexpr bool low_latency_mode = true; return reinterpret_cast(new OggOpusEncoder( num_channels, sample_rate_hz, bitrate_bits_per_second, use_vbr, low_latency_mode)); } JNIEXPORT jbyteArray JNICALL JNI_METHOD(processAudioBytes)(JNIEnv* env, jobject instance, jlong instance_ptr, jbyteArray samples, jint offset, jint length) { if (!VerifyInitialized("processAudioBytes", instance_ptr)) { return convertToByteArray(std::vector(0), env); } jsize array_length_bytes = length; if (!array_length_bytes) { fprintf(stdout, "Found empty array\n"); } assert(array_length_bytes % 2 == 0 && "int16 formatted stream missing bytes!"); std::vector pcm(array_length_bytes / 2); env->GetByteArrayRegion(samples, offset, array_length_bytes, reinterpret_cast(pcm.data())); if (env->ExceptionOccurred()) { fprintf(stderr, "Exception occurred in java Environment object\n"); } return convertToByteArray(GetInstanceOrDie(instance_ptr)->Process(pcm), env); } JNIEXPORT jbyteArray JNICALL JNI_METHOD(flush)(JNIEnv* env, jobject instance, jlong instance_ptr) { return convertToByteArray(GetInstanceOrDie(instance_ptr)->Flush(), env); } JNIEXPORT void JNICALL JNI_METHOD(free)(JNIEnv* env, jobject instance, jlong instance_ptr) { delete reinterpret_cast(instance_ptr); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/jni/ogg_opus_encoder.h ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef AUDIO_UTIL_JNI_OGG_OPUS_ENCODER_H_ #define AUDIO_UTIL_JNI_OGG_OPUS_ENCODER_H_ #include // https://stackoverflow.com/questions/25363027/jni-getmethodid-not-working-for-constructor-of-inner-class #define JNI_METHOD(fn) \ Java_com_teamopensmartglasses_smartglassesmanager_speechrecognition_google_asr_asrhelpers_StreamingAudioEncoder_00024OggOpusEncoder_##fn // NOLINT extern "C" { // Create opus encoder instance. The pointer is returned in a // jlong. Remember to call destroy with the returned value when you're done. JNIEXPORT jlong JNICALL JNI_METHOD(init)(JNIEnv* env, jobject instance, jint num_channels, jint bitrate_bits_per_second, jint sample_rate_hz, jboolean use_vbr); // samples must be an even number of bytes, as it represents 16-bit audio data. JNIEXPORT jbyteArray JNICALL JNI_METHOD(processAudioBytes)(JNIEnv* env, jobject instance, jlong instance_ptr, jbyteArray samples, jint offset, jint length); // Tell the encoder that there will be no more samples. JNIEXPORT jbyteArray JNICALL JNI_METHOD(flush)(JNIEnv* env, jobject instance, jlong instance_ptr); // Releases all resources. JNIEXPORT void JNICALL JNI_METHOD(free)(JNIEnv* env, jobject instance, jlong instance_ptr); } // extern "C" #endif // AUDIO_UTIL_JNI_OGG_OPUS_ENCODER_H_ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libogg/Makefile.am ================================================ ## Process this file with automake to produce Makefile.in oggincludedir = $(includedir)/ogg ogginclude_HEADERS = ogg.h os_types.h nodist_ogginclude_HEADERS = config_types.h ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libogg/config_types.h ================================================ #ifndef __CONFIG_TYPES_H__ #define __CONFIG_TYPES_H__ /* these are filled in by configure or cmake*/ #define INCLUDE_INTTYPES_H 1 #define INCLUDE_STDINT_H 1 #define INCLUDE_SYS_TYPES_H 1 #if INCLUDE_INTTYPES_H # include #endif #if INCLUDE_STDINT_H # include #endif #if INCLUDE_SYS_TYPES_H # include #endif typedef int16_t ogg_int16_t; typedef uint16_t ogg_uint16_t; typedef int32_t ogg_int32_t; typedef uint32_t ogg_uint32_t; typedef int64_t ogg_int64_t; typedef uint64_t ogg_uint64_t; #endif ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libogg/config_types.h.in ================================================ #ifndef __CONFIG_TYPES_H__ #define __CONFIG_TYPES_H__ /* these are filled in by configure or cmake*/ #define INCLUDE_INTTYPES_H @INCLUDE_INTTYPES_H@ #define INCLUDE_STDINT_H @INCLUDE_STDINT_H@ #define INCLUDE_SYS_TYPES_H @INCLUDE_SYS_TYPES_H@ #if INCLUDE_INTTYPES_H # include #endif #if INCLUDE_STDINT_H # include #endif #if INCLUDE_SYS_TYPES_H # include #endif typedef @SIZE16@ ogg_int16_t; typedef @USIZE16@ ogg_uint16_t; typedef @SIZE32@ ogg_int32_t; typedef @USIZE32@ ogg_uint32_t; typedef @SIZE64@ ogg_int64_t; typedef @USIZE64@ ogg_uint64_t; #endif ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libogg/ogg.h ================================================ /******************************************************************** * * * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * * * * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2007 * * by the Xiph.Org Foundation http://www.xiph.org/ * * * ******************************************************************** function: toplevel libogg include ********************************************************************/ #ifndef _OGG_H #define _OGG_H #ifdef __cplusplus extern "C" { #endif #include #include "os_types.h" typedef struct { void *iov_base; size_t iov_len; } ogg_iovec_t; typedef struct { long endbyte; int endbit; unsigned char *buffer; unsigned char *ptr; long storage; } oggpack_buffer; /* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ typedef struct { unsigned char *header; long header_len; unsigned char *body; long body_len; } ogg_page; /* ogg_stream_state contains the current encode/decode state of a logical Ogg bitstream **********************************************************/ typedef struct { unsigned char *body_data; /* bytes from packet bodies */ long body_storage; /* storage elements allocated */ long body_fill; /* elements stored; fill mark */ long body_returned; /* elements of fill returned */ int *lacing_vals; /* The values that will go to the segment table */ ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact this way, but it is simple coupled to the lacing fifo */ long lacing_storage; long lacing_fill; long lacing_packet; long lacing_returned; unsigned char header[282]; /* working space for header encode */ int header_fill; int e_o_s; /* set when we have buffered the last packet in the logical bitstream */ int b_o_s; /* set after we've written the initial page of a logical bitstream */ long serialno; long pageno; ogg_int64_t packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a separate abstraction layer) also knows about the gap */ ogg_int64_t granulepos; } ogg_stream_state; /* ogg_packet is used to encapsulate the data and metadata belonging to a single raw Ogg/Vorbis packet *************************************/ typedef struct { unsigned char *packet; long bytes; long b_o_s; long e_o_s; ogg_int64_t granulepos; ogg_int64_t packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a separate abstraction layer) also knows about the gap */ } ogg_packet; typedef struct { unsigned char *data; int storage; int fill; int returned; int unsynced; int headerbytes; int bodybytes; } ogg_sync_state; /* Ogg BITSTREAM PRIMITIVES: bitstream ************************/ extern void oggpack_writeinit(oggpack_buffer *b); extern int oggpack_writecheck(oggpack_buffer *b); extern void oggpack_writetrunc(oggpack_buffer *b,long bits); extern void oggpack_writealign(oggpack_buffer *b); extern void oggpack_writecopy(oggpack_buffer *b,void *source,long bits); extern void oggpack_reset(oggpack_buffer *b); extern void oggpack_writeclear(oggpack_buffer *b); extern void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); extern void oggpack_write(oggpack_buffer *b,unsigned long value,int bits); extern long oggpack_look(oggpack_buffer *b,int bits); extern long oggpack_look1(oggpack_buffer *b); extern void oggpack_adv(oggpack_buffer *b,int bits); extern void oggpack_adv1(oggpack_buffer *b); extern long oggpack_read(oggpack_buffer *b,int bits); extern long oggpack_read1(oggpack_buffer *b); extern long oggpack_bytes(oggpack_buffer *b); extern long oggpack_bits(oggpack_buffer *b); extern unsigned char *oggpack_get_buffer(oggpack_buffer *b); extern void oggpackB_writeinit(oggpack_buffer *b); extern int oggpackB_writecheck(oggpack_buffer *b); extern void oggpackB_writetrunc(oggpack_buffer *b,long bits); extern void oggpackB_writealign(oggpack_buffer *b); extern void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits); extern void oggpackB_reset(oggpack_buffer *b); extern void oggpackB_writeclear(oggpack_buffer *b); extern void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); extern void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits); extern long oggpackB_look(oggpack_buffer *b,int bits); extern long oggpackB_look1(oggpack_buffer *b); extern void oggpackB_adv(oggpack_buffer *b,int bits); extern void oggpackB_adv1(oggpack_buffer *b); extern long oggpackB_read(oggpack_buffer *b,int bits); extern long oggpackB_read1(oggpack_buffer *b); extern long oggpackB_bytes(oggpack_buffer *b); extern long oggpackB_bits(oggpack_buffer *b); extern unsigned char *oggpackB_get_buffer(oggpack_buffer *b); /* Ogg BITSTREAM PRIMITIVES: encoding **************************/ extern int ogg_stream_packetin(ogg_stream_state *os, ogg_packet *op); extern int ogg_stream_iovecin(ogg_stream_state *os, ogg_iovec_t *iov, int count, long e_o_s, ogg_int64_t granulepos); extern int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og); extern int ogg_stream_pageout_fill(ogg_stream_state *os, ogg_page *og, int nfill); extern int ogg_stream_flush(ogg_stream_state *os, ogg_page *og); extern int ogg_stream_flush_fill(ogg_stream_state *os, ogg_page *og, int nfill); /* Ogg BITSTREAM PRIMITIVES: decoding **************************/ extern int ogg_sync_init(ogg_sync_state *oy); extern int ogg_sync_clear(ogg_sync_state *oy); extern int ogg_sync_reset(ogg_sync_state *oy); extern int ogg_sync_destroy(ogg_sync_state *oy); extern int ogg_sync_check(ogg_sync_state *oy); extern char *ogg_sync_buffer(ogg_sync_state *oy, long size); extern int ogg_sync_wrote(ogg_sync_state *oy, long bytes); extern long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og); extern int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og); extern int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og); extern int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op); extern int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op); /* Ogg BITSTREAM PRIMITIVES: general ***************************/ extern int ogg_stream_init(ogg_stream_state *os,int serialno); extern int ogg_stream_clear(ogg_stream_state *os); extern int ogg_stream_reset(ogg_stream_state *os); extern int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno); extern int ogg_stream_destroy(ogg_stream_state *os); extern int ogg_stream_check(ogg_stream_state *os); extern int ogg_stream_eos(ogg_stream_state *os); extern void ogg_page_checksum_set(ogg_page *og); extern int ogg_page_version(const ogg_page *og); extern int ogg_page_continued(const ogg_page *og); extern int ogg_page_bos(const ogg_page *og); extern int ogg_page_eos(const ogg_page *og); extern ogg_int64_t ogg_page_granulepos(const ogg_page *og); extern int ogg_page_serialno(const ogg_page *og); extern long ogg_page_pageno(const ogg_page *og); extern int ogg_page_packets(const ogg_page *og); extern void ogg_packet_clear(ogg_packet *op); #ifdef __cplusplus } #endif #endif /* _OGG_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libogg/os_types.h ================================================ /******************************************************************** * * * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * * * * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * * by the Xiph.Org Foundation http://www.xiph.org/ * * * ******************************************************************** function: Define a consistent set of types on each platform. ********************************************************************/ #ifndef _OS_TYPES_H #define _OS_TYPES_H /* make it easy on the folks that want to compile the libs with a different malloc than stdlib */ #define _ogg_malloc malloc #define _ogg_calloc calloc #define _ogg_realloc realloc #define _ogg_free free #if defined(_WIN32) # if defined(__CYGWIN__) # include typedef int16_t ogg_int16_t; typedef uint16_t ogg_uint16_t; typedef int32_t ogg_int32_t; typedef uint32_t ogg_uint32_t; typedef int64_t ogg_int64_t; typedef uint64_t ogg_uint64_t; # elif defined(__MINGW32__) # include typedef short ogg_int16_t; typedef unsigned short ogg_uint16_t; typedef int ogg_int32_t; typedef unsigned int ogg_uint32_t; typedef long long ogg_int64_t; typedef unsigned long long ogg_uint64_t; # elif defined(__MWERKS__) typedef long long ogg_int64_t; typedef unsigned long long ogg_uint64_t; typedef int ogg_int32_t; typedef unsigned int ogg_uint32_t; typedef short ogg_int16_t; typedef unsigned short ogg_uint16_t; # else # if defined(_MSC_VER) && (_MSC_VER >= 1800) /* MSVC 2013 and newer */ # include typedef int16_t ogg_int16_t; typedef uint16_t ogg_uint16_t; typedef int32_t ogg_int32_t; typedef uint32_t ogg_uint32_t; typedef int64_t ogg_int64_t; typedef uint64_t ogg_uint64_t; # else /* MSVC/Borland */ typedef __int64 ogg_int64_t; typedef __int32 ogg_int32_t; typedef unsigned __int32 ogg_uint32_t; typedef unsigned __int64 ogg_uint64_t; typedef __int16 ogg_int16_t; typedef unsigned __int16 ogg_uint16_t; # endif # endif #elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ # include typedef int16_t ogg_int16_t; typedef uint16_t ogg_uint16_t; typedef int32_t ogg_int32_t; typedef uint32_t ogg_uint32_t; typedef int64_t ogg_int64_t; typedef uint64_t ogg_uint64_t; #elif defined(__HAIKU__) /* Haiku */ # include typedef short ogg_int16_t; typedef unsigned short ogg_uint16_t; typedef int ogg_int32_t; typedef unsigned int ogg_uint32_t; typedef long long ogg_int64_t; typedef unsigned long long ogg_uint64_t; #elif defined(__BEOS__) /* Be */ # include typedef int16_t ogg_int16_t; typedef uint16_t ogg_uint16_t; typedef int32_t ogg_int32_t; typedef uint32_t ogg_uint32_t; typedef int64_t ogg_int64_t; typedef uint64_t ogg_uint64_t; #elif defined (__EMX__) /* OS/2 GCC */ typedef short ogg_int16_t; typedef unsigned short ogg_uint16_t; typedef int ogg_int32_t; typedef unsigned int ogg_uint32_t; typedef long long ogg_int64_t; typedef unsigned long long ogg_uint64_t; #elif defined (DJGPP) /* DJGPP */ typedef short ogg_int16_t; typedef int ogg_int32_t; typedef unsigned int ogg_uint32_t; typedef long long ogg_int64_t; typedef unsigned long long ogg_uint64_t; #elif defined(R5900) /* PS2 EE */ typedef long ogg_int64_t; typedef unsigned long ogg_uint64_t; typedef int ogg_int32_t; typedef unsigned ogg_uint32_t; typedef short ogg_int16_t; #elif defined(__SYMBIAN32__) /* Symbian GCC */ typedef signed short ogg_int16_t; typedef unsigned short ogg_uint16_t; typedef signed int ogg_int32_t; typedef unsigned int ogg_uint32_t; typedef long long int ogg_int64_t; typedef unsigned long long int ogg_uint64_t; #elif defined(__TMS320C6X__) /* TI C64x compiler */ typedef signed short ogg_int16_t; typedef unsigned short ogg_uint16_t; typedef signed int ogg_int32_t; typedef unsigned int ogg_uint32_t; typedef long long int ogg_int64_t; typedef unsigned long long int ogg_uint64_t; #else # include "config_types.h" #endif #endif /* _OS_TYPES_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libopus/opus.h ================================================ /* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited Written by Jean-Marc Valin and Koen Vos */ /* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @file opus.h * @brief Opus reference implementation API */ #ifndef OPUS_H #define OPUS_H #include "opus_types.h" #include "opus_defines.h" #ifdef __cplusplus extern "C" { #endif /** * @mainpage Opus * * The Opus codec is designed for interactive speech and audio transmission over the Internet. * It is designed by the IETF Codec Working Group and incorporates technology from * Skype's SILK codec and Xiph.Org's CELT codec. * * The Opus codec is designed to handle a wide range of interactive audio applications, * including Voice over IP, videoconferencing, in-game chat, and even remote live music * performances. It can scale from low bit-rate narrowband speech to very high quality * stereo music. Its main features are: * @li Sampling rates from 8 to 48 kHz * @li Bit-rates from 6 kb/s to 510 kb/s * @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR) * @li Audio bandwidth from narrowband to full-band * @li Support for speech and music * @li Support for mono and stereo * @li Support for multichannel (up to 255 channels) * @li Frame sizes from 2.5 ms to 60 ms * @li Good loss robustness and packet loss concealment (PLC) * @li Floating point and fixed-point implementation * * Documentation sections: * @li @ref opus_encoder * @li @ref opus_decoder * @li @ref opus_repacketizer * @li @ref opus_multistream * @li @ref opus_libinfo * @li @ref opus_custom */ /** @defgroup opus_encoder Opus Encoder * @{ * * @brief This page describes the process and functions used to encode Opus. * * Since Opus is a stateful codec, the encoding process starts with creating an encoder * state. This can be done with: * * @code * int error; * OpusEncoder *enc; * enc = opus_encoder_create(Fs, channels, application, &error); * @endcode * * From this point, @c enc can be used for encoding an audio stream. An encoder state * @b must @b not be used for more than one stream at the same time. Similarly, the encoder * state @b must @b not be re-initialized for each frame. * * While opus_encoder_create() allocates memory for the state, it's also possible * to initialize pre-allocated memory: * * @code * int size; * int error; * OpusEncoder *enc; * size = opus_encoder_get_size(channels); * enc = malloc(size); * error = opus_encoder_init(enc, Fs, channels, application); * @endcode * * where opus_encoder_get_size() returns the required size for the encoder state. Note that * future versions of this code may change the size, so no assuptions should be made about it. * * The encoder state is always continuous in memory and only a shallow copy is sufficient * to copy it (e.g. memcpy()) * * It is possible to change some of the encoder's settings using the opus_encoder_ctl() * interface. All these settings already default to the recommended value, so they should * only be changed when necessary. The most common settings one may want to change are: * * @code * opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); * opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); * opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type)); * @endcode * * where * * @arg bitrate is in bits per second (b/s) * @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest * @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC * * See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream. * * To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data: * @code * len = opus_encode(enc, audio_frame, frame_size, packet, max_packet); * @endcode * * where *
    *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • *
  • frame_size is the duration of the frame in samples (per channel)
  • *
  • packet is the byte array to which the compressed data is written
  • *
  • max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended). * Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.
  • *
* * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. * The return value can be negative, which indicates that an error has occurred. If the return value * is 2 bytes or less, then the packet does not need to be transmitted (DTX). * * Once the encoder state if no longer needed, it can be destroyed with * * @code * opus_encoder_destroy(enc); * @endcode * * If the encoder was created with opus_encoder_init() rather than opus_encoder_create(), * then no action is required aside from potentially freeing the memory that was manually * allocated for it (calling free(enc) for the example above) * */ /** Opus encoder state. * This contains the complete state of an Opus encoder. * It is position independent and can be freely copied. * @see opus_encoder_create,opus_encoder_init */ typedef struct OpusEncoder OpusEncoder; /** Gets the size of an OpusEncoder structure. * @param[in] channels int: Number of channels. * This must be 1 or 2. * @returns The size in bytes. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels); /** */ /** Allocates and initializes an encoder state. * There are three coding modes: * * @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice * signals. It enhances the input signal by high-pass filtering and * emphasizing formants and harmonics. Optionally it includes in-band * forward error correction to protect against packet loss. Use this * mode for typical VoIP applications. Because of the enhancement, * even at high bitrates the output may sound different from the input. * * @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most * non-voice signals like music. Use this mode for music and mixed * (music/voice) content, broadcast, and applications requiring less * than 15 ms of coding delay. * * @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that * disables the speech-optimized mode in exchange for slightly reduced delay. * This mode can only be set on an newly initialized or freshly reset encoder * because it changes the codec delay. * * This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution). * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param [in] channels int: Number of channels (1 or 2) in input signal * @param [in] application int: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) * @param [out] error int*: @ref opus_errorcodes * @note Regardless of the sampling rate and number channels selected, the Opus encoder * can switch to a lower audio bandwidth or number of channels if the bitrate * selected is too low. This also means that it is safe to always use 48 kHz stereo input * and let the encoder optimize the encoding. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create( opus_int32 Fs, int channels, int application, int *error ); /** Initializes a previously allocated encoder state * The memory pointed to by st must be at least the size returned by opus_encoder_get_size(). * This is intended for applications which use their own allocator instead of malloc. * @see opus_encoder_create(),opus_encoder_get_size() * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. * @param [in] st OpusEncoder*: Encoder state * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param [in] channels int: Number of channels (1 or 2) in input signal * @param [in] application int: Coding mode (OPUS_APPLICATION_VOIP/OPUS_APPLICATION_AUDIO/OPUS_APPLICATION_RESTRICTED_LOWDELAY) * @retval #OPUS_OK Success or @ref opus_errorcodes */ OPUS_EXPORT int opus_encoder_init( OpusEncoder *st, opus_int32 Fs, int channels, int application ) OPUS_ARG_NONNULL(1); /** Encodes an Opus frame. * @param [in] st OpusEncoder*: Encoder state * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) * @param [in] frame_size int: Number of samples per channel in the * input signal. * This must be an Opus frame size for * the encoder's sampling rate. * For example, at 48 kHz the permitted * values are 120, 240, 480, 960, 1920, * and 2880. * Passing in a duration of less than * 10 ms (480 samples at 48 kHz) will * prevent the encoder from using the LPC * or hybrid modes. * @param [out] data unsigned char*: Output payload. * This must contain storage for at * least \a max_data_bytes. * @param [in] max_data_bytes opus_int32: Size of the allocated * memory for the output * payload. This may be * used to impose an upper limit on * the instant bitrate, but should * not be used as the only bitrate * control. Use #OPUS_SET_BITRATE to * control the bitrate. * @returns The length of the encoded packet (in bytes) on success or a * negative error code (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode( OpusEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Encodes an Opus frame from floating point input. * @param [in] st OpusEncoder*: Encoder state * @param [in] pcm float*: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0. * Samples with a range beyond +/-1.0 are supported but will * be clipped by decoders using the integer API and should * only be used if it is known that the far end supports * extended dynamic range. * length is frame_size*channels*sizeof(float) * @param [in] frame_size int: Number of samples per channel in the * input signal. * This must be an Opus frame size for * the encoder's sampling rate. * For example, at 48 kHz the permitted * values are 120, 240, 480, 960, 1920, * and 2880. * Passing in a duration of less than * 10 ms (480 samples at 48 kHz) will * prevent the encoder from using the LPC * or hybrid modes. * @param [out] data unsigned char*: Output payload. * This must contain storage for at * least \a max_data_bytes. * @param [in] max_data_bytes opus_int32: Size of the allocated * memory for the output * payload. This may be * used to impose an upper limit on * the instant bitrate, but should * not be used as the only bitrate * control. Use #OPUS_SET_BITRATE to * control the bitrate. * @returns The length of the encoded packet (in bytes) on success or a * negative error code (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float( OpusEncoder *st, const float *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Frees an OpusEncoder allocated by opus_encoder_create(). * @param[in] st OpusEncoder*: State to be freed. */ OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st); /** Perform a CTL function on an Opus encoder. * * Generally the request and subsequent arguments are generated * by a convenience macro. * @param st OpusEncoder*: Encoder state. * @param request This and all remaining parameters should be replaced by one * of the convenience macros in @ref opus_genericctls or * @ref opus_encoderctls. * @see opus_genericctls * @see opus_encoderctls */ OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); /**@}*/ /** @defgroup opus_decoder Opus Decoder * @{ * * @brief This page describes the process and functions used to decode Opus. * * The decoding process also starts with creating a decoder * state. This can be done with: * @code * int error; * OpusDecoder *dec; * dec = opus_decoder_create(Fs, channels, &error); * @endcode * where * @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000 * @li channels is the number of channels (1 or 2) * @li error will hold the error code in case of failure (or #OPUS_OK on success) * @li the return value is a newly created decoder state to be used for decoding * * While opus_decoder_create() allocates memory for the state, it's also possible * to initialize pre-allocated memory: * @code * int size; * int error; * OpusDecoder *dec; * size = opus_decoder_get_size(channels); * dec = malloc(size); * error = opus_decoder_init(dec, Fs, channels); * @endcode * where opus_decoder_get_size() returns the required size for the decoder state. Note that * future versions of this code may change the size, so no assuptions should be made about it. * * The decoder state is always continuous in memory and only a shallow copy is sufficient * to copy it (e.g. memcpy()) * * To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data: * @code * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); * @endcode * where * * @li packet is the byte array containing the compressed data * @li len is the exact number of bytes contained in the packet * @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) * @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array * * opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet. * If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio * buffer is too small to hold the decoded audio. * * Opus is a stateful codec with overlapping blocks and as a result Opus * packets are not coded independently of each other. Packets must be * passed into the decoder serially and in the correct order for a correct * decode. Lost packets can be replaced with loss concealment by calling * the decoder with a null pointer and zero length for the missing packet. * * A single codec state may only be accessed from a single thread at * a time and any required locking must be performed by the caller. Separate * streams must be decoded with separate decoder states and can be decoded * in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK * defined. * */ /** Opus decoder state. * This contains the complete state of an Opus decoder. * It is position independent and can be freely copied. * @see opus_decoder_create,opus_decoder_init */ typedef struct OpusDecoder OpusDecoder; /** Gets the size of an OpusDecoder structure. * @param [in] channels int: Number of channels. * This must be 1 or 2. * @returns The size in bytes. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels); /** Allocates and initializes a decoder state. * @param [in] Fs opus_int32: Sample rate to decode at (Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param [in] channels int: Number of channels (1 or 2) to decode * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes * * Internally Opus stores data at 48000 Hz, so that should be the default * value for Fs. However, the decoder can efficiently decode to buffers * at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use * data at the full sample rate, or knows the compressed data doesn't * use the full frequency range, it can request decoding at a reduced * rate. Likewise, the decoder is capable of filling in either mono or * interleaved stereo pcm buffers, at the caller's request. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create( opus_int32 Fs, int channels, int *error ); /** Initializes a previously allocated decoder state. * The state must be at least the size returned by opus_decoder_get_size(). * This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. * @param [in] st OpusDecoder*: Decoder state. * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param [in] channels int: Number of channels (1 or 2) to decode * @retval #OPUS_OK Success or @ref opus_errorcodes */ OPUS_EXPORT int opus_decoder_init( OpusDecoder *st, opus_int32 Fs, int channels ) OPUS_ARG_NONNULL(1); /** Decode an Opus packet. * @param [in] st OpusDecoder*: Decoder state * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss * @param [in] len opus_int32: Number of bytes in payload* * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length * is frame_size*channels*sizeof(opus_int16) * @param [in] frame_size Number of samples per channel of available space in \a pcm. * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), * then frame_size needs to be exactly the duration of audio that is missing, otherwise the * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and * FEC cases, frame_size must be a multiple of 2.5 ms. * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be * decoded. If no such data is available, the frame is decoded as if it were lost. * @returns Number of decoded samples or @ref opus_errorcodes */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode( OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Decode an Opus packet with floating point output. * @param [in] st OpusDecoder*: Decoder state * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss * @param [in] len opus_int32: Number of bytes in payload * @param [out] pcm float*: Output signal (interleaved if 2 channels). length * is frame_size*channels*sizeof(float) * @param [in] frame_size Number of samples per channel of available space in \a pcm. * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), * then frame_size needs to be exactly the duration of audio that is missing, otherwise the * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and * FEC cases, frame_size must be a multiple of 2.5 ms. * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be * decoded. If no such data is available the frame is decoded as if it were lost. * @returns Number of decoded samples or @ref opus_errorcodes */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float( OpusDecoder *st, const unsigned char *data, opus_int32 len, float *pcm, int frame_size, int decode_fec ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Perform a CTL function on an Opus decoder. * * Generally the request and subsequent arguments are generated * by a convenience macro. * @param st OpusDecoder*: Decoder state. * @param request This and all remaining parameters should be replaced by one * of the convenience macros in @ref opus_genericctls or * @ref opus_decoderctls. * @see opus_genericctls * @see opus_decoderctls */ OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); /** Frees an OpusDecoder allocated by opus_decoder_create(). * @param[in] st OpusDecoder*: State to be freed. */ OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st); /** Parse an opus packet into one or more frames. * Opus_decode will perform this operation internally so most applications do * not need to use this function. * This function does not copy the frames, the returned pointers are pointers into * the input packet. * @param [in] data char*: Opus packet to be parsed * @param [in] len opus_int32: size of data * @param [out] out_toc char*: TOC pointer * @param [out] frames char*[48] encapsulated frames * @param [out] size opus_int16[48] sizes of the encapsulated frames * @param [out] payload_offset int*: returns the position of the payload within the packet (in bytes) * @returns number of frames */ OPUS_EXPORT int opus_packet_parse( const unsigned char *data, opus_int32 len, unsigned char *out_toc, const unsigned char *frames[48], opus_int16 size[48], int *payload_offset ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5); /** Gets the bandwidth of an Opus packet. * @param [in] data char*: Opus packet * @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass) * @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass) * @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass) * @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass) * @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass) * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1); /** Gets the number of samples per frame from an Opus packet. * @param [in] data char*: Opus packet. * This must contain at least one byte of * data. * @param [in] Fs opus_int32: Sampling rate in Hz. * This must be a multiple of 400, or * inaccurate results will be returned. * @returns Number of samples per frame. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1); /** Gets the number of channels from an Opus packet. * @param [in] data char*: Opus packet * @returns Number of channels * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1); /** Gets the number of frames in an Opus packet. * @param [in] packet char*: Opus packet * @param [in] len opus_int32: Length of packet * @returns Number of frames * @retval OPUS_BAD_ARG Insufficient data was passed to the function * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1); /** Gets the number of samples of an Opus packet. * @param [in] packet char*: Opus packet * @param [in] len opus_int32: Length of packet * @param [in] Fs opus_int32: Sampling rate in Hz. * This must be a multiple of 400, or * inaccurate results will be returned. * @returns Number of samples * @retval OPUS_BAD_ARG Insufficient data was passed to the function * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1); /** Gets the number of samples of an Opus packet. * @param [in] dec OpusDecoder*: Decoder state * @param [in] packet char*: Opus packet * @param [in] len opus_int32: Length of packet * @returns Number of samples * @retval OPUS_BAD_ARG Insufficient data was passed to the function * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); /** Applies soft-clipping to bring a float signal within the [-1,1] range. If * the signal is already in that range, nothing is done. If there are values * outside of [-1,1], then the signal is clipped as smoothly as possible to * both fit in the range and avoid creating excessive distortion in the * process. * @param [in,out] pcm float*: Input PCM and modified PCM * @param [in] frame_size int Number of samples per channel to process * @param [in] channels int: Number of channels * @param [in,out] softclip_mem float*: State memory for the soft clipping process (one float per channel, initialized to zero) */ OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem); /**@}*/ /** @defgroup opus_repacketizer Repacketizer * @{ * * The repacketizer can be used to merge multiple Opus packets into a single * packet or alternatively to split Opus packets that have previously been * merged. Splitting valid Opus packets is always guaranteed to succeed, * whereas merging valid packets only succeeds if all frames have the same * mode, bandwidth, and frame size, and when the total duration of the merged * packet is no more than 120 ms. The 120 ms limit comes from the * specification and limits decoder memory requirements at a point where * framing overhead becomes negligible. * * The repacketizer currently only operates on elementary Opus * streams. It will not manipualte multistream packets successfully, except in * the degenerate case where they consist of data from a single stream. * * The repacketizing process starts with creating a repacketizer state, either * by calling opus_repacketizer_create() or by allocating the memory yourself, * e.g., * @code * OpusRepacketizer *rp; * rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size()); * if (rp != NULL) * opus_repacketizer_init(rp); * @endcode * * Then the application should submit packets with opus_repacketizer_cat(), * extract new packets with opus_repacketizer_out() or * opus_repacketizer_out_range(), and then reset the state for the next set of * input packets via opus_repacketizer_init(). * * For example, to split a sequence of packets into individual frames: * @code * unsigned char *data; * int len; * while (get_next_packet(&data, &len)) * { * unsigned char out[1276]; * opus_int32 out_len; * int nb_frames; * int err; * int i; * err = opus_repacketizer_cat(rp, data, len); * if (err != OPUS_OK) * { * release_packet(data); * return err; * } * nb_frames = opus_repacketizer_get_nb_frames(rp); * for (i = 0; i < nb_frames; i++) * { * out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out)); * if (out_len < 0) * { * release_packet(data); * return (int)out_len; * } * output_next_packet(out, out_len); * } * opus_repacketizer_init(rp); * release_packet(data); * } * @endcode * * Alternatively, to combine a sequence of frames into packets that each * contain up to TARGET_DURATION_MS milliseconds of data: * @code * // The maximum number of packets with duration TARGET_DURATION_MS occurs * // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5) * // packets. * unsigned char *data[(TARGET_DURATION_MS*2/5)+1]; * opus_int32 len[(TARGET_DURATION_MS*2/5)+1]; * int nb_packets; * unsigned char out[1277*(TARGET_DURATION_MS*2/2)]; * opus_int32 out_len; * int prev_toc; * nb_packets = 0; * while (get_next_packet(data+nb_packets, len+nb_packets)) * { * int nb_frames; * int err; * nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]); * if (nb_frames < 1) * { * release_packets(data, nb_packets+1); * return nb_frames; * } * nb_frames += opus_repacketizer_get_nb_frames(rp); * // If adding the next packet would exceed our target, or it has an * // incompatible TOC sequence, output the packets we already have before * // submitting it. * // N.B., The nb_packets > 0 check ensures we've submitted at least one * // packet since the last call to opus_repacketizer_init(). Otherwise a * // single packet longer than TARGET_DURATION_MS would cause us to try to * // output an (invalid) empty packet. It also ensures that prev_toc has * // been set to a valid value. Additionally, len[nb_packets] > 0 is * // guaranteed by the call to opus_packet_get_nb_frames() above, so the * // reference to data[nb_packets][0] should be valid. * if (nb_packets > 0 && ( * ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) || * opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames > * TARGET_DURATION_MS*48)) * { * out_len = opus_repacketizer_out(rp, out, sizeof(out)); * if (out_len < 0) * { * release_packets(data, nb_packets+1); * return (int)out_len; * } * output_next_packet(out, out_len); * opus_repacketizer_init(rp); * release_packets(data, nb_packets); * data[0] = data[nb_packets]; * len[0] = len[nb_packets]; * nb_packets = 0; * } * err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]); * if (err != OPUS_OK) * { * release_packets(data, nb_packets+1); * return err; * } * prev_toc = data[nb_packets][0]; * nb_packets++; * } * // Output the final, partial packet. * if (nb_packets > 0) * { * out_len = opus_repacketizer_out(rp, out, sizeof(out)); * release_packets(data, nb_packets); * if (out_len < 0) * return (int)out_len; * output_next_packet(out, out_len); * } * @endcode * * An alternate way of merging packets is to simply call opus_repacketizer_cat() * unconditionally until it fails. At that point, the merged packet can be * obtained with opus_repacketizer_out() and the input packet for which * opus_repacketizer_cat() needs to be re-added to a newly reinitialized * repacketizer state. */ typedef struct OpusRepacketizer OpusRepacketizer; /** Gets the size of an OpusRepacketizer structure. * @returns The size in bytes. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void); /** (Re)initializes a previously allocated repacketizer state. * The state must be at least the size returned by opus_repacketizer_get_size(). * This can be used for applications which use their own allocator instead of * malloc(). * It must also be called to reset the queue of packets waiting to be * repacketized, which is necessary if the maximum packet duration of 120 ms * is reached or if you wish to submit packets with a different Opus * configuration (coding mode, audio bandwidth, frame size, or channel count). * Failure to do so will prevent a new packet from being added with * opus_repacketizer_cat(). * @see opus_repacketizer_create * @see opus_repacketizer_get_size * @see opus_repacketizer_cat * @param rp OpusRepacketizer*: The repacketizer state to * (re)initialize. * @returns A pointer to the same repacketizer state that was passed in. */ OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); /** Allocates memory and initializes the new repacketizer with * opus_repacketizer_init(). */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void); /** Frees an OpusRepacketizer allocated by * opus_repacketizer_create(). * @param[in] rp OpusRepacketizer*: State to be freed. */ OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp); /** Add a packet to the current repacketizer state. * This packet must match the configuration of any packets already submitted * for repacketization since the last call to opus_repacketizer_init(). * This means that it must have the same coding mode, audio bandwidth, frame * size, and channel count. * This can be checked in advance by examining the top 6 bits of the first * byte of the packet, and ensuring they match the top 6 bits of the first * byte of any previously submitted packet. * The total duration of audio in the repacketizer state also must not exceed * 120 ms, the maximum duration of a single packet, after adding this packet. * * The contents of the current repacketizer state can be extracted into new * packets using opus_repacketizer_out() or opus_repacketizer_out_range(). * * In order to add a packet with a different configuration or to add more * audio beyond 120 ms, you must clear the repacketizer state by calling * opus_repacketizer_init(). * If a packet is too large to add to the current repacketizer state, no part * of it is added, even if it contains multiple frames, some of which might * fit. * If you wish to be able to add parts of such packets, you should first use * another repacketizer to split the packet into pieces and add them * individually. * @see opus_repacketizer_out_range * @see opus_repacketizer_out * @see opus_repacketizer_init * @param rp OpusRepacketizer*: The repacketizer state to which to * add the packet. * @param[in] data const unsigned char*: The packet data. * The application must ensure * this pointer remains valid * until the next call to * opus_repacketizer_init() or * opus_repacketizer_destroy(). * @param len opus_int32: The number of bytes in the packet data. * @returns An error code indicating whether or not the operation succeeded. * @retval #OPUS_OK The packet's contents have been added to the repacketizer * state. * @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence, * the packet's TOC sequence was not compatible * with previously submitted packets (because * the coding mode, audio bandwidth, frame size, * or channel count did not match), or adding * this packet would increase the total amount of * audio stored in the repacketizer state to more * than 120 ms. */ OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); /** Construct a new packet from data previously submitted to the repacketizer * state via opus_repacketizer_cat(). * @param rp OpusRepacketizer*: The repacketizer state from which to * construct the new packet. * @param begin int: The index of the first frame in the current * repacketizer state to include in the output. * @param end int: One past the index of the last frame in the * current repacketizer state to include in the * output. * @param[out] data const unsigned char*: The buffer in which to * store the output packet. * @param maxlen opus_int32: The maximum number of bytes to store in * the output buffer. In order to guarantee * success, this should be at least * 1276 for a single frame, * or for multiple frames, * 1277*(end-begin). * However, 1*(end-begin) plus * the size of all packet data submitted to * the repacketizer since the last call to * opus_repacketizer_init() or * opus_repacketizer_create() is also * sufficient, and possibly much smaller. * @returns The total size of the output packet on success, or an error code * on failure. * @retval #OPUS_BAD_ARG [begin,end) was an invalid range of * frames (begin < 0, begin >= end, or end > * opus_repacketizer_get_nb_frames()). * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the * complete output packet. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Return the total number of frames contained in packet data submitted to * the repacketizer state so far via opus_repacketizer_cat() since the last * call to opus_repacketizer_init() or opus_repacketizer_create(). * This defines the valid range of packets that can be extracted with * opus_repacketizer_out_range() or opus_repacketizer_out(). * @param rp OpusRepacketizer*: The repacketizer state containing the * frames. * @returns The total number of frames contained in the packet data submitted * to the repacketizer state. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); /** Construct a new packet from data previously submitted to the repacketizer * state via opus_repacketizer_cat(). * This is a convenience routine that returns all the data submitted so far * in a single packet. * It is equivalent to calling * @code * opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp), * data, maxlen) * @endcode * @param rp OpusRepacketizer*: The repacketizer state from which to * construct the new packet. * @param[out] data const unsigned char*: The buffer in which to * store the output packet. * @param maxlen opus_int32: The maximum number of bytes to store in * the output buffer. In order to guarantee * success, this should be at least * 1277*opus_repacketizer_get_nb_frames(rp). * However, * 1*opus_repacketizer_get_nb_frames(rp) * plus the size of all packet data * submitted to the repacketizer since the * last call to opus_repacketizer_init() or * opus_repacketizer_create() is also * sufficient, and possibly much smaller. * @returns The total size of the output packet on success, or an error code * on failure. * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the * complete output packet. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1); /** Pads a given Opus packet to a larger size (possibly changing the TOC sequence). * @param[in,out] data const unsigned char*: The buffer containing the * packet to pad. * @param len opus_int32: The size of the packet. * This must be at least 1. * @param new_len opus_int32: The desired size of the packet after padding. * This must be at least as large as len. * @returns an error code * @retval #OPUS_OK \a on success. * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. */ OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len); /** Remove all padding from a given Opus packet and rewrite the TOC sequence to * minimize space usage. * @param[in,out] data const unsigned char*: The buffer containing the * packet to strip. * @param len opus_int32: The size of the packet. * This must be at least 1. * @returns The new size of the output packet on success, or an error code * on failure. * @retval #OPUS_BAD_ARG \a len was less than 1. * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len); /** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence). * @param[in,out] data const unsigned char*: The buffer containing the * packet to pad. * @param len opus_int32: The size of the packet. * This must be at least 1. * @param new_len opus_int32: The desired size of the packet after padding. * This must be at least 1. * @param nb_streams opus_int32: The number of streams (not channels) in the packet. * This must be at least as large as len. * @returns an error code * @retval #OPUS_OK \a on success. * @retval #OPUS_BAD_ARG \a len was less than 1. * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. */ OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams); /** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to * minimize space usage. * @param[in,out] data const unsigned char*: The buffer containing the * packet to strip. * @param len opus_int32: The size of the packet. * This must be at least 1. * @param nb_streams opus_int32: The number of streams (not channels) in the packet. * This must be at least 1. * @returns The new size of the output packet on success, or an error code * on failure. * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams); /**@}*/ #ifdef __cplusplus } #endif #endif /* OPUS_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libopus/opus_custom.h ================================================ /* Copyright (c) 2007-2008 CSIRO Copyright (c) 2007-2009 Xiph.Org Foundation Copyright (c) 2008-2012 Gregory Maxwell Written by Jean-Marc Valin and Gregory Maxwell */ /* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file opus_custom.h @brief Opus-Custom reference implementation API */ #ifndef OPUS_CUSTOM_H #define OPUS_CUSTOM_H #include "opus_defines.h" #ifdef __cplusplus extern "C" { #endif #ifdef CUSTOM_MODES # define OPUS_CUSTOM_EXPORT OPUS_EXPORT # define OPUS_CUSTOM_EXPORT_STATIC OPUS_EXPORT #else # define OPUS_CUSTOM_EXPORT # ifdef OPUS_BUILD # define OPUS_CUSTOM_EXPORT_STATIC static OPUS_INLINE # else # define OPUS_CUSTOM_EXPORT_STATIC # endif #endif /** @defgroup opus_custom Opus Custom * @{ * Opus Custom is an optional part of the Opus specification and * reference implementation which uses a distinct API from the regular * API and supports frame sizes that are not normally supported.\ Use * of Opus Custom is discouraged for all but very special applications * for which a frame size different from 2.5, 5, 10, or 20 ms is needed * (for either complexity or latency reasons) and where interoperability * is less important. * * In addition to the interoperability limitations the use of Opus custom * disables a substantial chunk of the codec and generally lowers the * quality available at a given bitrate. Normally when an application needs * a different frame size from the codec it should buffer to match the * sizes but this adds a small amount of delay which may be important * in some very low latency applications. Some transports (especially * constant rate RF transports) may also work best with frames of * particular durations. * * Libopus only supports custom modes if they are enabled at compile time. * * The Opus Custom API is similar to the regular API but the * @ref opus_encoder_create and @ref opus_decoder_create calls take * an additional mode parameter which is a structure produced by * a call to @ref opus_custom_mode_create. Both the encoder and decoder * must create a mode using the same sample rate (fs) and frame size * (frame size) so these parameters must either be signaled out of band * or fixed in a particular implementation. * * Similar to regular Opus the custom modes support on the fly frame size * switching, but the sizes available depend on the particular frame size in * use. For some initial frame sizes on a single on the fly size is available. */ /** Contains the state of an encoder. One encoder state is needed for each stream. It is initialized once at the beginning of the stream. Do *not* re-initialize the state for every frame. @brief Encoder state */ typedef struct OpusCustomEncoder OpusCustomEncoder; /** State of the decoder. One decoder state is needed for each stream. It is initialized once at the beginning of the stream. Do *not* re-initialize the state for every frame. @brief Decoder state */ typedef struct OpusCustomDecoder OpusCustomDecoder; /** The mode contains all the information necessary to create an encoder. Both the encoder and decoder need to be initialized with exactly the same mode, otherwise the output will be corrupted. @brief Mode configuration */ typedef struct OpusCustomMode OpusCustomMode; /** Creates a new mode struct. This will be passed to an encoder or * decoder. The mode MUST NOT BE DESTROYED until the encoders and * decoders that use it are destroyed as well. * @param [in] Fs int: Sampling rate (8000 to 96000 Hz) * @param [in] frame_size int: Number of samples (per channel) to encode in each * packet (64 - 1024, prime factorization must contain zero or more 2s, 3s, or 5s and no other primes) * @param [out] error int*: Returned error code (if NULL, no error will be returned) * @return A newly created mode */ OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error); /** Destroys a mode struct. Only call this after all encoders and * decoders using this mode are destroyed as well. * @param [in] mode OpusCustomMode*: Mode to be freed. */ OPUS_CUSTOM_EXPORT void opus_custom_mode_destroy(OpusCustomMode *mode); #if !defined(OPUS_BUILD) || defined(CELT_ENCODER_C) /* Encoder */ /** Gets the size of an OpusCustomEncoder structure. * @param [in] mode OpusCustomMode *: Mode configuration * @param [in] channels int: Number of channels * @returns size */ OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_encoder_get_size( const OpusCustomMode *mode, int channels ) OPUS_ARG_NONNULL(1); # ifdef CUSTOM_MODES /** Initializes a previously allocated encoder state * The memory pointed to by st must be the size returned by opus_custom_encoder_get_size. * This is intended for applications which use their own allocator instead of malloc. * @see opus_custom_encoder_create(),opus_custom_encoder_get_size() * To reset a previously initialized state use the OPUS_RESET_STATE CTL. * @param [in] st OpusCustomEncoder*: Encoder state * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of * the stream (must be the same characteristics as used for the * decoder) * @param [in] channels int: Number of channels * @return OPUS_OK Success or @ref opus_errorcodes */ OPUS_CUSTOM_EXPORT int opus_custom_encoder_init( OpusCustomEncoder *st, const OpusCustomMode *mode, int channels ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); # endif #endif /** Creates a new encoder state. Each stream needs its own encoder * state (can't be shared across simultaneous streams). * @param [in] mode OpusCustomMode*: Contains all the information about the characteristics of * the stream (must be the same characteristics as used for the * decoder) * @param [in] channels int: Number of channels * @param [out] error int*: Returns an error code * @return Newly created encoder state. */ OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomEncoder *opus_custom_encoder_create( const OpusCustomMode *mode, int channels, int *error ) OPUS_ARG_NONNULL(1); /** Destroys a an encoder state. * @param[in] st OpusCustomEncoder*: State to be freed. */ OPUS_CUSTOM_EXPORT void opus_custom_encoder_destroy(OpusCustomEncoder *st); /** Encodes a frame of audio. * @param [in] st OpusCustomEncoder*: Encoder state * @param [in] pcm float*: PCM audio in float format, with a normal range of +/-1.0. * Samples with a range beyond +/-1.0 are supported but will * be clipped by decoders using the integer API and should * only be used if it is known that the far end supports * extended dynamic range. There must be exactly * frame_size samples per channel. * @param [in] frame_size int: Number of samples per frame of input signal * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame * (can change from one frame to another) * @return Number of bytes written to "compressed". * If negative, an error has occurred (see error codes). It is IMPORTANT that * the length returned be somehow transmitted to the decoder. Otherwise, no * decoding is possible. */ OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode_float( OpusCustomEncoder *st, const float *pcm, int frame_size, unsigned char *compressed, int maxCompressedBytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Encodes a frame of audio. * @param [in] st OpusCustomEncoder*: Encoder state * @param [in] pcm opus_int16*: PCM audio in signed 16-bit format (native endian). * There must be exactly frame_size samples per channel. * @param [in] frame_size int: Number of samples per frame of input signal * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame * (can change from one frame to another) * @return Number of bytes written to "compressed". * If negative, an error has occurred (see error codes). It is IMPORTANT that * the length returned be somehow transmitted to the decoder. Otherwise, no * decoding is possible. */ OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode( OpusCustomEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *compressed, int maxCompressedBytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Perform a CTL function on an Opus custom encoder. * * Generally the request and subsequent arguments are generated * by a convenience macro. * @see opus_encoderctls */ OPUS_CUSTOM_EXPORT int opus_custom_encoder_ctl(OpusCustomEncoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); #if !defined(OPUS_BUILD) || defined(CELT_DECODER_C) /* Decoder */ /** Gets the size of an OpusCustomDecoder structure. * @param [in] mode OpusCustomMode *: Mode configuration * @param [in] channels int: Number of channels * @returns size */ OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_decoder_get_size( const OpusCustomMode *mode, int channels ) OPUS_ARG_NONNULL(1); /** Initializes a previously allocated decoder state * The memory pointed to by st must be the size returned by opus_custom_decoder_get_size. * This is intended for applications which use their own allocator instead of malloc. * @see opus_custom_decoder_create(),opus_custom_decoder_get_size() * To reset a previously initialized state use the OPUS_RESET_STATE CTL. * @param [in] st OpusCustomDecoder*: Decoder state * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of * the stream (must be the same characteristics as used for the * encoder) * @param [in] channels int: Number of channels * @return OPUS_OK Success or @ref opus_errorcodes */ OPUS_CUSTOM_EXPORT_STATIC int opus_custom_decoder_init( OpusCustomDecoder *st, const OpusCustomMode *mode, int channels ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); #endif /** Creates a new decoder state. Each stream needs its own decoder state (can't * be shared across simultaneous streams). * @param [in] mode OpusCustomMode: Contains all the information about the characteristics of the * stream (must be the same characteristics as used for the encoder) * @param [in] channels int: Number of channels * @param [out] error int*: Returns an error code * @return Newly created decoder state. */ OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomDecoder *opus_custom_decoder_create( const OpusCustomMode *mode, int channels, int *error ) OPUS_ARG_NONNULL(1); /** Destroys a an decoder state. * @param[in] st OpusCustomDecoder*: State to be freed. */ OPUS_CUSTOM_EXPORT void opus_custom_decoder_destroy(OpusCustomDecoder *st); /** Decode an opus custom frame with floating point output * @param [in] st OpusCustomDecoder*: Decoder state * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss * @param [in] len int: Number of bytes in payload * @param [out] pcm float*: Output signal (interleaved if 2 channels). length * is frame_size*channels*sizeof(float) * @param [in] frame_size Number of samples per channel of available space in *pcm. * @returns Number of decoded samples or @ref opus_errorcodes */ OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode_float( OpusCustomDecoder *st, const unsigned char *data, int len, float *pcm, int frame_size ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Decode an opus custom frame * @param [in] st OpusCustomDecoder*: Decoder state * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss * @param [in] len int: Number of bytes in payload * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length * is frame_size*channels*sizeof(opus_int16) * @param [in] frame_size Number of samples per channel of available space in *pcm. * @returns Number of decoded samples or @ref opus_errorcodes */ OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode( OpusCustomDecoder *st, const unsigned char *data, int len, opus_int16 *pcm, int frame_size ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Perform a CTL function on an Opus custom decoder. * * Generally the request and subsequent arguments are generated * by a convenience macro. * @see opus_genericctls */ OPUS_CUSTOM_EXPORT int opus_custom_decoder_ctl(OpusCustomDecoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); /**@}*/ #ifdef __cplusplus } #endif #endif /* OPUS_CUSTOM_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libopus/opus_defines.h ================================================ /* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited Written by Jean-Marc Valin and Koen Vos */ /* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @file opus_defines.h * @brief Opus reference implementation constants */ #ifndef OPUS_DEFINES_H #define OPUS_DEFINES_H #include "opus_types.h" #ifdef __cplusplus extern "C" { #endif /** @defgroup opus_errorcodes Error codes * @{ */ /** No error @hideinitializer*/ #define OPUS_OK 0 /** One or more invalid/out of range arguments @hideinitializer*/ #define OPUS_BAD_ARG -1 /** Not enough bytes allocated in the buffer @hideinitializer*/ #define OPUS_BUFFER_TOO_SMALL -2 /** An internal error was detected @hideinitializer*/ #define OPUS_INTERNAL_ERROR -3 /** The compressed data passed is corrupted @hideinitializer*/ #define OPUS_INVALID_PACKET -4 /** Invalid/unsupported request number @hideinitializer*/ #define OPUS_UNIMPLEMENTED -5 /** An encoder or decoder structure is invalid or already freed @hideinitializer*/ #define OPUS_INVALID_STATE -6 /** Memory allocation has failed @hideinitializer*/ #define OPUS_ALLOC_FAIL -7 /**@}*/ /** @cond OPUS_INTERNAL_DOC */ /**Export control for opus functions */ #ifndef OPUS_EXPORT # if defined(WIN32) # if defined(OPUS_BUILD) && defined(DLL_EXPORT) # define OPUS_EXPORT __declspec(dllexport) # else # define OPUS_EXPORT # endif # elif defined(__GNUC__) && defined(OPUS_BUILD) # define OPUS_EXPORT __attribute__ ((visibility ("default"))) # else # define OPUS_EXPORT # endif #endif # if !defined(OPUS_GNUC_PREREQ) # if defined(__GNUC__)&&defined(__GNUC_MINOR__) # define OPUS_GNUC_PREREQ(_maj,_min) \ ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) # else # define OPUS_GNUC_PREREQ(_maj,_min) 0 # endif # endif #if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) # if OPUS_GNUC_PREREQ(3,0) # define OPUS_RESTRICT __restrict__ # elif (defined(_MSC_VER) && _MSC_VER >= 1400) # define OPUS_RESTRICT __restrict # else # define OPUS_RESTRICT # endif #else # define OPUS_RESTRICT restrict #endif #if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) # if OPUS_GNUC_PREREQ(2,7) # define OPUS_INLINE __inline__ # elif (defined(_MSC_VER)) # define OPUS_INLINE __inline # else # define OPUS_INLINE # endif #else # define OPUS_INLINE inline #endif /**Warning attributes for opus functions * NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out * some paranoid null checks. */ #if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) # define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) #else # define OPUS_WARN_UNUSED_RESULT #endif #if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) # define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x))) #else # define OPUS_ARG_NONNULL(_x) #endif /** These are the actual Encoder CTL ID numbers. * They should not be used directly by applications. * In general, SETs should be even and GETs should be odd.*/ #define OPUS_SET_APPLICATION_REQUEST 4000 #define OPUS_GET_APPLICATION_REQUEST 4001 #define OPUS_SET_BITRATE_REQUEST 4002 #define OPUS_GET_BITRATE_REQUEST 4003 #define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004 #define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005 #define OPUS_SET_VBR_REQUEST 4006 #define OPUS_GET_VBR_REQUEST 4007 #define OPUS_SET_BANDWIDTH_REQUEST 4008 #define OPUS_GET_BANDWIDTH_REQUEST 4009 #define OPUS_SET_COMPLEXITY_REQUEST 4010 #define OPUS_GET_COMPLEXITY_REQUEST 4011 #define OPUS_SET_INBAND_FEC_REQUEST 4012 #define OPUS_GET_INBAND_FEC_REQUEST 4013 #define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014 #define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015 #define OPUS_SET_DTX_REQUEST 4016 #define OPUS_GET_DTX_REQUEST 4017 #define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020 #define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021 #define OPUS_SET_FORCE_CHANNELS_REQUEST 4022 #define OPUS_GET_FORCE_CHANNELS_REQUEST 4023 #define OPUS_SET_SIGNAL_REQUEST 4024 #define OPUS_GET_SIGNAL_REQUEST 4025 #define OPUS_GET_LOOKAHEAD_REQUEST 4027 /* #define OPUS_RESET_STATE 4028 */ #define OPUS_GET_SAMPLE_RATE_REQUEST 4029 #define OPUS_GET_FINAL_RANGE_REQUEST 4031 #define OPUS_GET_PITCH_REQUEST 4033 #define OPUS_SET_GAIN_REQUEST 4034 #define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */ #define OPUS_SET_LSB_DEPTH_REQUEST 4036 #define OPUS_GET_LSB_DEPTH_REQUEST 4037 #define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039 #define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040 #define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041 #define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042 #define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043 /* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */ #define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046 #define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047 #define OPUS_GET_IN_DTX_REQUEST 4049 /** Defines for the presence of extended APIs. */ #define OPUS_HAVE_OPUS_PROJECTION_H /* Macros to trigger compilation errors when the wrong types are provided to a CTL */ #define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x)) #define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr))) #define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr))) #define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr))) /** @endcond */ /** @defgroup opus_ctlvalues Pre-defined values for CTL interface * @see opus_genericctls, opus_encoderctls * @{ */ /* Values for the various encoder CTLs */ #define OPUS_AUTO -1000 /**opus_int32: Allowed values: 0-10, inclusive. * * @hideinitializer */ #define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x) /** Gets the encoder's complexity configuration. * @see OPUS_SET_COMPLEXITY * @param[out] x opus_int32 *: Returns a value in the range 0-10, * inclusive. * @hideinitializer */ #define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x) /** Configures the bitrate in the encoder. * Rates from 500 to 512000 bits per second are meaningful, as well as the * special values #OPUS_AUTO and #OPUS_BITRATE_MAX. * The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much * rate as it can, which is useful for controlling the rate by adjusting the * output buffer size. * @see OPUS_GET_BITRATE * @param[in] x opus_int32: Bitrate in bits per second. The default * is determined based on the number of * channels and the input sampling rate. * @hideinitializer */ #define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x) /** Gets the encoder's bitrate configuration. * @see OPUS_SET_BITRATE * @param[out] x opus_int32 *: Returns the bitrate in bits per second. * The default is determined based on the * number of channels and the input * sampling rate. * @hideinitializer */ #define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x) /** Enables or disables variable bitrate (VBR) in the encoder. * The configured bitrate may not be met exactly because frames must * be an integer number of bytes in length. * @see OPUS_GET_VBR * @see OPUS_SET_VBR_CONSTRAINT * @param[in] x opus_int32: Allowed values: *
*
0
Hard CBR. For LPC/hybrid modes at very low bit-rate, this can * cause noticeable quality degradation.
*
1
VBR (default). The exact type of VBR is controlled by * #OPUS_SET_VBR_CONSTRAINT.
*
* @hideinitializer */ #define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x) /** Determine if variable bitrate (VBR) is enabled in the encoder. * @see OPUS_SET_VBR * @see OPUS_GET_VBR_CONSTRAINT * @param[out] x opus_int32 *: Returns one of the following values: *
*
0
Hard CBR.
*
1
VBR (default). The exact type of VBR may be retrieved via * #OPUS_GET_VBR_CONSTRAINT.
*
* @hideinitializer */ #define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x) /** Enables or disables constrained VBR in the encoder. * This setting is ignored when the encoder is in CBR mode. * @warning Only the MDCT mode of Opus currently heeds the constraint. * Speech mode ignores it completely, hybrid mode may fail to obey it * if the LPC layer uses more bitrate than the constraint would have * permitted. * @see OPUS_GET_VBR_CONSTRAINT * @see OPUS_SET_VBR * @param[in] x opus_int32: Allowed values: *
*
0
Unconstrained VBR.
*
1
Constrained VBR (default). This creates a maximum of one * frame of buffering delay assuming a transport with a * serialization speed of the nominal bitrate.
*
* @hideinitializer */ #define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x) /** Determine if constrained VBR is enabled in the encoder. * @see OPUS_SET_VBR_CONSTRAINT * @see OPUS_GET_VBR * @param[out] x opus_int32 *: Returns one of the following values: *
*
0
Unconstrained VBR.
*
1
Constrained VBR (default).
*
* @hideinitializer */ #define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x) /** Configures mono/stereo forcing in the encoder. * This can force the encoder to produce packets encoded as either mono or * stereo, regardless of the format of the input audio. This is useful when * the caller knows that the input signal is currently a mono source embedded * in a stereo stream. * @see OPUS_GET_FORCE_CHANNELS * @param[in] x opus_int32: Allowed values: *
*
#OPUS_AUTO
Not forced (default)
*
1
Forced mono
*
2
Forced stereo
*
* @hideinitializer */ #define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x) /** Gets the encoder's forced channel configuration. * @see OPUS_SET_FORCE_CHANNELS * @param[out] x opus_int32 *: *
*
#OPUS_AUTO
Not forced (default)
*
1
Forced mono
*
2
Forced stereo
*
* @hideinitializer */ #define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x) /** Configures the maximum bandpass that the encoder will select automatically. * Applications should normally use this instead of #OPUS_SET_BANDWIDTH * (leaving that set to the default, #OPUS_AUTO). This allows the * application to set an upper bound based on the type of input it is * providing, but still gives the encoder the freedom to reduce the bandpass * when the bitrate becomes too low, for better overall quality. * @see OPUS_GET_MAX_BANDWIDTH * @param[in] x opus_int32: Allowed values: *
*
OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
*
OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
*
OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
*
OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
*
OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
*
* @hideinitializer */ #define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x) /** Gets the encoder's configured maximum allowed bandpass. * @see OPUS_SET_MAX_BANDWIDTH * @param[out] x opus_int32 *: Allowed values: *
*
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
*
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
*
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
*
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
*
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
*
* @hideinitializer */ #define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) /** Sets the encoder's bandpass to a specific value. * This prevents the encoder from automatically selecting the bandpass based * on the available bitrate. If an application knows the bandpass of the input * audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH * instead, which still gives the encoder the freedom to reduce the bandpass * when the bitrate becomes too low, for better overall quality. * @see OPUS_GET_BANDWIDTH * @param[in] x opus_int32: Allowed values: *
*
#OPUS_AUTO
(default)
*
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
*
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
*
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
*
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
*
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
*
* @hideinitializer */ #define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x) /** Configures the type of signal being encoded. * This is a hint which helps the encoder's mode selection. * @see OPUS_GET_SIGNAL * @param[in] x opus_int32: Allowed values: *
*
#OPUS_AUTO
(default)
*
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
*
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
*
* @hideinitializer */ #define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x) /** Gets the encoder's configured signal type. * @see OPUS_SET_SIGNAL * @param[out] x opus_int32 *: Returns one of the following values: *
*
#OPUS_AUTO
(default)
*
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
*
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
*
* @hideinitializer */ #define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x) /** Configures the encoder's intended application. * The initial value is a mandatory argument to the encoder_create function. * @see OPUS_GET_APPLICATION * @param[in] x opus_int32: Returns one of the following values: *
*
#OPUS_APPLICATION_VOIP
*
Process signal for improved speech intelligibility.
*
#OPUS_APPLICATION_AUDIO
*
Favor faithfulness to the original input.
*
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
*
Configure the minimum possible coding delay by disabling certain modes * of operation.
*
* @hideinitializer */ #define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x) /** Gets the encoder's configured application. * @see OPUS_SET_APPLICATION * @param[out] x opus_int32 *: Returns one of the following values: *
*
#OPUS_APPLICATION_VOIP
*
Process signal for improved speech intelligibility.
*
#OPUS_APPLICATION_AUDIO
*
Favor faithfulness to the original input.
*
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
*
Configure the minimum possible coding delay by disabling certain modes * of operation.
*
* @hideinitializer */ #define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x) /** Gets the total samples of delay added by the entire codec. * This can be queried by the encoder and then the provided number of samples can be * skipped on from the start of the decoder's output to provide time aligned input * and output. From the perspective of a decoding application the real data begins this many * samples late. * * The decoder contribution to this delay is identical for all decoders, but the * encoder portion of the delay may vary from implementation to implementation, * version to version, or even depend on the encoder's initial configuration. * Applications needing delay compensation should call this CTL rather than * hard-coding a value. * @param[out] x opus_int32 *: Number of lookahead samples * @hideinitializer */ #define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x) /** Configures the encoder's use of inband forward error correction (FEC). * @note This is only applicable to the LPC layer * @see OPUS_GET_INBAND_FEC * @param[in] x opus_int32: Allowed values: *
*
0
Disable inband FEC (default).
*
1
Enable inband FEC.
*
* @hideinitializer */ #define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x) /** Gets encoder's configured use of inband forward error correction. * @see OPUS_SET_INBAND_FEC * @param[out] x opus_int32 *: Returns one of the following values: *
*
0
Inband FEC disabled (default).
*
1
Inband FEC enabled.
*
* @hideinitializer */ #define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x) /** Configures the encoder's expected packet loss percentage. * Higher values trigger progressively more loss resistant behavior in the encoder * at the expense of quality at a given bitrate in the absence of packet loss, but * greater quality under loss. * @see OPUS_GET_PACKET_LOSS_PERC * @param[in] x opus_int32: Loss percentage in the range 0-100, inclusive (default: 0). * @hideinitializer */ #define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x) /** Gets the encoder's configured packet loss percentage. * @see OPUS_SET_PACKET_LOSS_PERC * @param[out] x opus_int32 *: Returns the configured loss percentage * in the range 0-100, inclusive (default: 0). * @hideinitializer */ #define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x) /** Configures the encoder's use of discontinuous transmission (DTX). * @note This is only applicable to the LPC layer * @see OPUS_GET_DTX * @param[in] x opus_int32: Allowed values: *
*
0
Disable DTX (default).
*
1
Enabled DTX.
*
* @hideinitializer */ #define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x) /** Gets encoder's configured use of discontinuous transmission. * @see OPUS_SET_DTX * @param[out] x opus_int32 *: Returns one of the following values: *
*
0
DTX disabled (default).
*
1
DTX enabled.
*
* @hideinitializer */ #define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x) /** Configures the depth of signal being encoded. * * This is a hint which helps the encoder identify silence and near-silence. * It represents the number of significant bits of linear intensity below * which the signal contains ignorable quantization or other noise. * * For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting * for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate * for 16-bit linear pcm input with opus_encode_float(). * * When using opus_encode() instead of opus_encode_float(), or when libopus * is compiled for fixed-point, the encoder uses the minimum of the value * set here and the value 16. * * @see OPUS_GET_LSB_DEPTH * @param[in] x opus_int32: Input precision in bits, between 8 and 24 * (default: 24). * @hideinitializer */ #define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x) /** Gets the encoder's configured signal depth. * @see OPUS_SET_LSB_DEPTH * @param[out] x opus_int32 *: Input precision in bits, between 8 and * 24 (default: 24). * @hideinitializer */ #define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x) /** Configures the encoder's use of variable duration frames. * When variable duration is enabled, the encoder is free to use a shorter frame * size than the one requested in the opus_encode*() call. * It is then the user's responsibility * to verify how much audio was encoded by checking the ToC byte of the encoded * packet. The part of the audio that was not encoded needs to be resent to the * encoder for the next call. Do not use this option unless you really * know what you are doing. * @see OPUS_GET_EXPERT_FRAME_DURATION * @param[in] x opus_int32: Allowed values: *
*
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
*
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
*
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
*
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
*
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
*
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
*
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
*
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
*
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
*
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
*
* @hideinitializer */ #define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x) /** Gets the encoder's configured use of variable duration frames. * @see OPUS_SET_EXPERT_FRAME_DURATION * @param[out] x opus_int32 *: Returns one of the following values: *
*
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
*
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
*
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
*
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
*
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
*
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
*
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
*
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
*
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
*
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
*
* @hideinitializer */ #define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x) /** If set to 1, disables almost all use of prediction, making frames almost * completely independent. This reduces quality. * @see OPUS_GET_PREDICTION_DISABLED * @param[in] x opus_int32: Allowed values: *
*
0
Enable prediction (default).
*
1
Disable prediction.
*
* @hideinitializer */ #define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x) /** Gets the encoder's configured prediction status. * @see OPUS_SET_PREDICTION_DISABLED * @param[out] x opus_int32 *: Returns one of the following values: *
*
0
Prediction enabled (default).
*
1
Prediction disabled.
*
* @hideinitializer */ #define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x) /**@}*/ /** @defgroup opus_genericctls Generic CTLs * * These macros are used with the \c opus_decoder_ctl and * \c opus_encoder_ctl calls to generate a particular * request. * * When called on an \c OpusDecoder they apply to that * particular decoder instance. When called on an * \c OpusEncoder they apply to the corresponding setting * on that encoder instance, if present. * * Some usage examples: * * @code * int ret; * opus_int32 pitch; * ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch)); * if (ret == OPUS_OK) return ret; * * opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE); * opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE); * * opus_int32 enc_bw, dec_bw; * opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw)); * opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw)); * if (enc_bw != dec_bw) { * printf("packet bandwidth mismatch!\n"); * } * @endcode * * @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls * @{ */ /** Resets the codec state to be equivalent to a freshly initialized state. * This should be called when switching streams in order to prevent * the back to back decoding from giving different results from * one at a time decoding. * @hideinitializer */ #define OPUS_RESET_STATE 4028 /** Gets the final state of the codec's entropy coder. * This is used for testing purposes, * The encoder and decoder state should be identical after coding a payload * (assuming no data corruption or software bugs) * * @param[out] x opus_uint32 *: Entropy coder state * * @hideinitializer */ #define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x) /** Gets the encoder's configured bandpass or the decoder's last bandpass. * @see OPUS_SET_BANDWIDTH * @param[out] x opus_int32 *: Returns one of the following values: *
*
#OPUS_AUTO
(default)
*
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
*
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
*
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
*
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
*
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
*
* @hideinitializer */ #define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) /** Gets the sampling rate the encoder or decoder was initialized with. * This simply returns the Fs value passed to opus_encoder_init() * or opus_decoder_init(). * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. * @hideinitializer */ #define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) /** If set to 1, disables the use of phase inversion for intensity stereo, * improving the quality of mono downmixes, but slightly reducing normal * stereo quality. Disabling phase inversion in the decoder does not comply * with RFC 6716, although it does not cause any interoperability issue and * is expected to become part of the Opus standard once RFC 6716 is updated * by draft-ietf-codec-opus-update. * @see OPUS_GET_PHASE_INVERSION_DISABLED * @param[in] x opus_int32: Allowed values: *
*
0
Enable phase inversion (default).
*
1
Disable phase inversion.
*
* @hideinitializer */ #define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x) /** Gets the encoder's configured phase inversion status. * @see OPUS_SET_PHASE_INVERSION_DISABLED * @param[out] x opus_int32 *: Returns one of the following values: *
*
0
Stereo phase inversion enabled (default).
*
1
Stereo phase inversion disabled.
*
* @hideinitializer */ #define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x) /** Gets the DTX state of the encoder. * Returns whether the last encoded frame was either a comfort noise update * during DTX or not encoded because of DTX. * @param[out] x opus_int32 *: Returns one of the following values: *
*
0
The encoder is not in DTX.
*
1
The encoder is in DTX.
*
* @hideinitializer */ #define OPUS_GET_IN_DTX(x) OPUS_GET_IN_DTX_REQUEST, __opus_check_int_ptr(x) /**@}*/ /** @defgroup opus_decoderctls Decoder related CTLs * @see opus_genericctls, opus_encoderctls, opus_decoder * @{ */ /** Configures decoder gain adjustment. * Scales the decoded output by a factor specified in Q8 dB units. * This has a maximum range of -32768 to 32767 inclusive, and returns * OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment. * This setting survives decoder reset. * * gain = pow(10, x/(20.0*256)) * * @param[in] x opus_int32: Amount to scale PCM signal by in Q8 dB units. * @hideinitializer */ #define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x) /** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN * * @param[out] x opus_int32 *: Amount to scale PCM signal by in Q8 dB units. * @hideinitializer */ #define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x) /** Gets the duration (in samples) of the last packet successfully decoded or concealed. * @param[out] x opus_int32 *: Number of samples (at current sampling rate). * @hideinitializer */ #define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) /** Gets the pitch of the last decoded frame, if available. * This can be used for any post-processing algorithm requiring the use of pitch, * e.g. time stretching/shortening. If the last frame was not voiced, or if the * pitch was not coded in the frame, then zero is returned. * * This CTL is only implemented for decoder instances. * * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) * * @hideinitializer */ #define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) /**@}*/ /** @defgroup opus_libinfo Opus library information functions * @{ */ /** Converts an opus error code into a human readable string. * * @param[in] error int: Error number * @returns Error string */ OPUS_EXPORT const char *opus_strerror(int error); /** Gets the libopus version string. * * Applications may look for the substring "-fixed" in the version string to * determine whether they have a fixed-point or floating-point build at * runtime. * * @returns Version string */ OPUS_EXPORT const char *opus_get_version_string(void); /**@}*/ #ifdef __cplusplus } #endif #endif /* OPUS_DEFINES_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libopus/opus_multistream.h ================================================ /* Copyright (c) 2011 Xiph.Org Foundation Written by Jean-Marc Valin */ /* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @file opus_multistream.h * @brief Opus reference implementation multistream API */ #ifndef OPUS_MULTISTREAM_H #define OPUS_MULTISTREAM_H #include "opus.h" #ifdef __cplusplus extern "C" { #endif /** @cond OPUS_INTERNAL_DOC */ /** Macros to trigger compilation errors when the wrong types are provided to a * CTL. */ /**@{*/ #define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr))) #define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr))) /**@}*/ /** These are the actual encoder and decoder CTL ID numbers. * They should not be used directly by applications. * In general, SETs should be even and GETs should be odd.*/ /**@{*/ #define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120 #define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122 /**@}*/ /** @endcond */ /** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs * * These are convenience macros that are specific to the * opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl() * interface. * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and * @ref opus_decoderctls may be applied to a multistream encoder or decoder as * well. * In addition, you may retrieve the encoder or decoder state for an specific * stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or * #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually. */ /**@{*/ /** Gets the encoder state for an individual stream of a multistream encoder. * @param[in] x opus_int32: The index of the stream whose encoder you * wish to retrieve. * This must be non-negative and less than * the streams parameter used * to initialize the encoder. * @param[out] y OpusEncoder**: Returns a pointer to the given * encoder state. * @retval OPUS_BAD_ARG The index of the requested stream was out of range. * @hideinitializer */ #define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y) /** Gets the decoder state for an individual stream of a multistream decoder. * @param[in] x opus_int32: The index of the stream whose decoder you * wish to retrieve. * This must be non-negative and less than * the streams parameter used * to initialize the decoder. * @param[out] y OpusDecoder**: Returns a pointer to the given * decoder state. * @retval OPUS_BAD_ARG The index of the requested stream was out of range. * @hideinitializer */ #define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y) /**@}*/ /** @defgroup opus_multistream Opus Multistream API * @{ * * The multistream API allows individual Opus streams to be combined into a * single packet, enabling support for up to 255 channels. Unlike an * elementary Opus stream, the encoder and decoder must negotiate the channel * configuration before the decoder can successfully interpret the data in the * packets produced by the encoder. Some basic information, such as packet * duration, can be computed without any special negotiation. * * The format for multistream Opus packets is defined in * RFC 7845 * and is based on the self-delimited Opus framing described in Appendix B of * RFC 6716. * Normal Opus packets are just a degenerate case of multistream Opus packets, * and can be encoded or decoded with the multistream API by setting * streams to 1 when initializing the encoder or * decoder. * * Multistream Opus streams can contain up to 255 elementary Opus streams. * These may be either "uncoupled" or "coupled", indicating that the decoder * is configured to decode them to either 1 or 2 channels, respectively. * The streams are ordered so that all coupled streams appear at the * beginning. * * A mapping table defines which decoded channel i * should be used for each input/output (I/O) channel j. This table is * typically provided as an unsigned char array. * Let i = mapping[j] be the index for I/O channel j. * If i < 2*coupled_streams, then I/O channel j is * encoded as the left channel of stream (i/2) if i * is even, or as the right channel of stream (i/2) if * i is odd. Otherwise, I/O channel j is encoded as * mono in stream (i - coupled_streams), unless it has the special * value 255, in which case it is omitted from the encoding entirely (the * decoder will reproduce it as silence). Each value i must either * be the special value 255 or be less than streams + coupled_streams. * * The output channels specified by the encoder * should use the * Vorbis * channel ordering. A decoder may wish to apply an additional permutation * to the mapping the encoder used to achieve a different output channel * order (e.g. for outputing in WAV order). * * Each multistream packet contains an Opus packet for each stream, and all of * the Opus packets in a single multistream packet must have the same * duration. Therefore the duration of a multistream packet can be extracted * from the TOC sequence of the first stream, which is located at the * beginning of the packet, just like an elementary Opus stream: * * @code * int nb_samples; * int nb_frames; * nb_frames = opus_packet_get_nb_frames(data, len); * if (nb_frames < 1) * return nb_frames; * nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames; * @endcode * * The general encoding and decoding process proceeds exactly the same as in * the normal @ref opus_encoder and @ref opus_decoder APIs. * See their documentation for an overview of how to use the corresponding * multistream functions. */ /** Opus multistream encoder state. * This contains the complete state of a multistream Opus encoder. * It is position independent and can be freely copied. * @see opus_multistream_encoder_create * @see opus_multistream_encoder_init */ typedef struct OpusMSEncoder OpusMSEncoder; /** Opus multistream decoder state. * This contains the complete state of a multistream Opus decoder. * It is position independent and can be freely copied. * @see opus_multistream_decoder_create * @see opus_multistream_decoder_init */ typedef struct OpusMSDecoder OpusMSDecoder; /**\name Multistream encoder functions */ /**@{*/ /** Gets the size of an OpusMSEncoder structure. * @param streams int: The total number of streams to encode from the * input. * This must be no more than 255. * @param coupled_streams int: Number of coupled (2 channel) streams * to encode. * This must be no larger than the total * number of streams. * Additionally, The total number of * encoded channels (streams + * coupled_streams) must be no * more than 255. * @returns The size in bytes on success, or a negative error code * (see @ref opus_errorcodes) on error. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size( int streams, int coupled_streams ); OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size( int channels, int mapping_family ); /** Allocates and initializes a multistream encoder state. * Call opus_multistream_encoder_destroy() to release * this object when finished. * @param Fs opus_int32: Sampling rate of the input signal (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels in the input signal. * This must be at most 255. * It may be greater than the number of * coded channels (streams + * coupled_streams). * @param streams int: The total number of streams to encode from the * input. * This must be no more than the number of channels. * @param coupled_streams int: Number of coupled (2 channel) streams * to encode. * This must be no larger than the total * number of streams. * Additionally, The total number of * encoded channels (streams + * coupled_streams) must be no * more than the number of input channels. * @param[in] mapping const unsigned char[channels]: Mapping from * encoded channels to input channels, as described in * @ref opus_multistream. As an extra constraint, the * multistream encoder does not allow encoding coupled * streams for which one channel is unused since this * is never a good idea. * @param application int: The target encoder application. * This must be one of the following: *
*
#OPUS_APPLICATION_VOIP
*
Process signal for improved speech intelligibility.
*
#OPUS_APPLICATION_AUDIO
*
Favor faithfulness to the original input.
*
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
*
Configure the minimum possible coding delay by disabling certain modes * of operation.
*
* @param[out] error int *: Returns #OPUS_OK on success, or an error * code (see @ref opus_errorcodes) on * failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create( opus_int32 Fs, int channels, int streams, int coupled_streams, const unsigned char *mapping, int application, int *error ) OPUS_ARG_NONNULL(5); OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create( opus_int32 Fs, int channels, int mapping_family, int *streams, int *coupled_streams, unsigned char *mapping, int application, int *error ) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); /** Initialize a previously allocated multistream encoder state. * The memory pointed to by \a st must be at least the size returned by * opus_multistream_encoder_get_size(). * This is intended for applications which use their own allocator instead of * malloc. * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. * @see opus_multistream_encoder_create * @see opus_multistream_encoder_get_size * @param st OpusMSEncoder*: Multistream encoder state to initialize. * @param Fs opus_int32: Sampling rate of the input signal (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels in the input signal. * This must be at most 255. * It may be greater than the number of * coded channels (streams + * coupled_streams). * @param streams int: The total number of streams to encode from the * input. * This must be no more than the number of channels. * @param coupled_streams int: Number of coupled (2 channel) streams * to encode. * This must be no larger than the total * number of streams. * Additionally, The total number of * encoded channels (streams + * coupled_streams) must be no * more than the number of input channels. * @param[in] mapping const unsigned char[channels]: Mapping from * encoded channels to input channels, as described in * @ref opus_multistream. As an extra constraint, the * multistream encoder does not allow encoding coupled * streams for which one channel is unused since this * is never a good idea. * @param application int: The target encoder application. * This must be one of the following: *
*
#OPUS_APPLICATION_VOIP
*
Process signal for improved speech intelligibility.
*
#OPUS_APPLICATION_AUDIO
*
Favor faithfulness to the original input.
*
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
*
Configure the minimum possible coding delay by disabling certain modes * of operation.
*
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) * on failure. */ OPUS_EXPORT int opus_multistream_encoder_init( OpusMSEncoder *st, opus_int32 Fs, int channels, int streams, int coupled_streams, const unsigned char *mapping, int application ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); OPUS_EXPORT int opus_multistream_surround_encoder_init( OpusMSEncoder *st, opus_int32 Fs, int channels, int mapping_family, int *streams, int *coupled_streams, unsigned char *mapping, int application ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7); /** Encodes a multistream Opus frame. * @param st OpusMSEncoder*: Multistream encoder state. * @param[in] pcm const opus_int16*: The input signal as interleaved * samples. * This must contain * frame_size*channels * samples. * @param frame_size int: Number of samples per channel in the input * signal. * This must be an Opus frame size for the * encoder's sampling rate. * For example, at 48 kHz the permitted values * are 120, 240, 480, 960, 1920, and 2880. * Passing in a duration of less than 10 ms * (480 samples at 48 kHz) will prevent the * encoder from using the LPC or hybrid modes. * @param[out] data unsigned char*: Output payload. * This must contain storage for at * least \a max_data_bytes. * @param [in] max_data_bytes opus_int32: Size of the allocated * memory for the output * payload. This may be * used to impose an upper limit on * the instant bitrate, but should * not be used as the only bitrate * control. Use #OPUS_SET_BITRATE to * control the bitrate. * @returns The length of the encoded packet (in bytes) on success or a * negative error code (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode( OpusMSEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Encodes a multistream Opus frame from floating point input. * @param st OpusMSEncoder*: Multistream encoder state. * @param[in] pcm const float*: The input signal as interleaved * samples with a normal range of * +/-1.0. * Samples with a range beyond +/-1.0 * are supported but will be clipped by * decoders using the integer API and * should only be used if it is known * that the far end supports extended * dynamic range. * This must contain * frame_size*channels * samples. * @param frame_size int: Number of samples per channel in the input * signal. * This must be an Opus frame size for the * encoder's sampling rate. * For example, at 48 kHz the permitted values * are 120, 240, 480, 960, 1920, and 2880. * Passing in a duration of less than 10 ms * (480 samples at 48 kHz) will prevent the * encoder from using the LPC or hybrid modes. * @param[out] data unsigned char*: Output payload. * This must contain storage for at * least \a max_data_bytes. * @param [in] max_data_bytes opus_int32: Size of the allocated * memory for the output * payload. This may be * used to impose an upper limit on * the instant bitrate, but should * not be used as the only bitrate * control. Use #OPUS_SET_BITRATE to * control the bitrate. * @returns The length of the encoded packet (in bytes) on success or a * negative error code (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float( OpusMSEncoder *st, const float *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Frees an OpusMSEncoder allocated by * opus_multistream_encoder_create(). * @param st OpusMSEncoder*: Multistream encoder state to be freed. */ OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st); /** Perform a CTL function on a multistream Opus encoder. * * Generally the request and subsequent arguments are generated by a * convenience macro. * @param st OpusMSEncoder*: Multistream encoder state. * @param request This and all remaining parameters should be replaced by one * of the convenience macros in @ref opus_genericctls, * @ref opus_encoderctls, or @ref opus_multistream_ctls. * @see opus_genericctls * @see opus_encoderctls * @see opus_multistream_ctls */ OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); /**@}*/ /**\name Multistream decoder functions */ /**@{*/ /** Gets the size of an OpusMSDecoder structure. * @param streams int: The total number of streams coded in the * input. * This must be no more than 255. * @param coupled_streams int: Number streams to decode as coupled * (2 channel) streams. * This must be no larger than the total * number of streams. * Additionally, The total number of * coded channels (streams + * coupled_streams) must be no * more than 255. * @returns The size in bytes on success, or a negative error code * (see @ref opus_errorcodes) on error. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size( int streams, int coupled_streams ); /** Allocates and initializes a multistream decoder state. * Call opus_multistream_decoder_destroy() to release * this object when finished. * @param Fs opus_int32: Sampling rate to decode at (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels to output. * This must be at most 255. * It may be different from the number of coded * channels (streams + * coupled_streams). * @param streams int: The total number of streams coded in the * input. * This must be no more than 255. * @param coupled_streams int: Number of streams to decode as coupled * (2 channel) streams. * This must be no larger than the total * number of streams. * Additionally, The total number of * coded channels (streams + * coupled_streams) must be no * more than 255. * @param[in] mapping const unsigned char[channels]: Mapping from * coded channels to output channels, as described in * @ref opus_multistream. * @param[out] error int *: Returns #OPUS_OK on success, or an error * code (see @ref opus_errorcodes) on * failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create( opus_int32 Fs, int channels, int streams, int coupled_streams, const unsigned char *mapping, int *error ) OPUS_ARG_NONNULL(5); /** Intialize a previously allocated decoder state object. * The memory pointed to by \a st must be at least the size returned by * opus_multistream_encoder_get_size(). * This is intended for applications which use their own allocator instead of * malloc. * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. * @see opus_multistream_decoder_create * @see opus_multistream_deocder_get_size * @param st OpusMSEncoder*: Multistream encoder state to initialize. * @param Fs opus_int32: Sampling rate to decode at (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels to output. * This must be at most 255. * It may be different from the number of coded * channels (streams + * coupled_streams). * @param streams int: The total number of streams coded in the * input. * This must be no more than 255. * @param coupled_streams int: Number of streams to decode as coupled * (2 channel) streams. * This must be no larger than the total * number of streams. * Additionally, The total number of * coded channels (streams + * coupled_streams) must be no * more than 255. * @param[in] mapping const unsigned char[channels]: Mapping from * coded channels to output channels, as described in * @ref opus_multistream. * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) * on failure. */ OPUS_EXPORT int opus_multistream_decoder_init( OpusMSDecoder *st, opus_int32 Fs, int channels, int streams, int coupled_streams, const unsigned char *mapping ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); /** Decode a multistream Opus packet. * @param st OpusMSDecoder*: Multistream decoder state. * @param[in] data const unsigned char*: Input payload. * Use a NULL * pointer to indicate packet * loss. * @param len opus_int32: Number of bytes in payload. * @param[out] pcm opus_int16*: Output signal, with interleaved * samples. * This must contain room for * frame_size*channels * samples. * @param frame_size int: The number of samples per channel of * available space in \a pcm. * If this is less than the maximum packet duration * (120 ms; 5760 for 48kHz), this function will not be capable * of decoding some packets. In the case of PLC (data==NULL) * or FEC (decode_fec=1), then frame_size needs to be exactly * the duration of audio that is missing, otherwise the * decoder will not be in the optimal state to decode the * next incoming packet. For the PLC and FEC cases, frame_size * must be a multiple of 2.5 ms. * @param decode_fec int: Flag (0 or 1) to request that any in-band * forward error correction data be decoded. * If no such data is available, the frame is * decoded as if it were lost. * @returns Number of samples decoded on success or a negative error code * (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode( OpusMSDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Decode a multistream Opus packet with floating point output. * @param st OpusMSDecoder*: Multistream decoder state. * @param[in] data const unsigned char*: Input payload. * Use a NULL * pointer to indicate packet * loss. * @param len opus_int32: Number of bytes in payload. * @param[out] pcm opus_int16*: Output signal, with interleaved * samples. * This must contain room for * frame_size*channels * samples. * @param frame_size int: The number of samples per channel of * available space in \a pcm. * If this is less than the maximum packet duration * (120 ms; 5760 for 48kHz), this function will not be capable * of decoding some packets. In the case of PLC (data==NULL) * or FEC (decode_fec=1), then frame_size needs to be exactly * the duration of audio that is missing, otherwise the * decoder will not be in the optimal state to decode the * next incoming packet. For the PLC and FEC cases, frame_size * must be a multiple of 2.5 ms. * @param decode_fec int: Flag (0 or 1) to request that any in-band * forward error correction data be decoded. * If no such data is available, the frame is * decoded as if it were lost. * @returns Number of samples decoded on success or a negative error code * (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float( OpusMSDecoder *st, const unsigned char *data, opus_int32 len, float *pcm, int frame_size, int decode_fec ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Perform a CTL function on a multistream Opus decoder. * * Generally the request and subsequent arguments are generated by a * convenience macro. * @param st OpusMSDecoder*: Multistream decoder state. * @param request This and all remaining parameters should be replaced by one * of the convenience macros in @ref opus_genericctls, * @ref opus_decoderctls, or @ref opus_multistream_ctls. * @see opus_genericctls * @see opus_decoderctls * @see opus_multistream_ctls */ OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); /** Frees an OpusMSDecoder allocated by * opus_multistream_decoder_create(). * @param st OpusMSDecoder: Multistream decoder state to be freed. */ OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); /**@}*/ /**@}*/ #ifdef __cplusplus } #endif #endif /* OPUS_MULTISTREAM_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libopus/opus_projection.h ================================================ /* Copyright (c) 2017 Google Inc. Written by Andrew Allen */ /* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @file opus_projection.h * @brief Opus projection reference API */ #ifndef OPUS_PROJECTION_H #define OPUS_PROJECTION_H #include "opus_multistream.h" #ifdef __cplusplus extern "C" { #endif /** @cond OPUS_INTERNAL_DOC */ /** These are the actual encoder and decoder CTL ID numbers. * They should not be used directly by applications.c * In general, SETs should be even and GETs should be odd.*/ /**@{*/ #define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST 6001 #define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST 6003 #define OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST 6005 /**@}*/ /** @endcond */ /** @defgroup opus_projection_ctls Projection specific encoder and decoder CTLs * * These are convenience macros that are specific to the * opus_projection_encoder_ctl() and opus_projection_decoder_ctl() * interface. * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, * @ref opus_decoderctls, and @ref opus_multistream_ctls may be applied to a * projection encoder or decoder as well. */ /**@{*/ /** Gets the gain (in dB. S7.8-format) of the demixing matrix from the encoder. * @param[out] x opus_int32 *: Returns the gain (in dB. S7.8-format) * of the demixing matrix. * @hideinitializer */ #define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST, __opus_check_int_ptr(x) /** Gets the size in bytes of the demixing matrix from the encoder. * @param[out] x opus_int32 *: Returns the size in bytes of the * demixing matrix. * @hideinitializer */ #define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, __opus_check_int_ptr(x) /** Copies the demixing matrix to the supplied pointer location. * @param[out] x unsigned char *: Returns the demixing matrix to the * supplied pointer location. * @param y opus_int32: The size in bytes of the reserved memory at the * pointer location. * @hideinitializer */ #define OPUS_PROJECTION_GET_DEMIXING_MATRIX(x,y) OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, x, __opus_check_int(y) /**@}*/ /** Opus projection encoder state. * This contains the complete state of a projection Opus encoder. * It is position independent and can be freely copied. * @see opus_projection_ambisonics_encoder_create */ typedef struct OpusProjectionEncoder OpusProjectionEncoder; /** Opus projection decoder state. * This contains the complete state of a projection Opus decoder. * It is position independent and can be freely copied. * @see opus_projection_decoder_create * @see opus_projection_decoder_init */ typedef struct OpusProjectionDecoder OpusProjectionDecoder; /**\name Projection encoder functions */ /**@{*/ /** Gets the size of an OpusProjectionEncoder structure. * @param channels int: The total number of input channels to encode. * This must be no more than 255. * @param mapping_family int: The mapping family to use for selecting * the appropriate projection. * @returns The size in bytes on success, or a negative error code * (see @ref opus_errorcodes) on error. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_ambisonics_encoder_get_size( int channels, int mapping_family ); /** Allocates and initializes a projection encoder state. * Call opus_projection_encoder_destroy() to release * this object when finished. * @param Fs opus_int32: Sampling rate of the input signal (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels in the input signal. * This must be at most 255. * It may be greater than the number of * coded channels (streams + * coupled_streams). * @param mapping_family int: The mapping family to use for selecting * the appropriate projection. * @param[out] streams int *: The total number of streams that will * be encoded from the input. * @param[out] coupled_streams int *: Number of coupled (2 channel) * streams that will be encoded from the input. * @param application int: The target encoder application. * This must be one of the following: *
*
#OPUS_APPLICATION_VOIP
*
Process signal for improved speech intelligibility.
*
#OPUS_APPLICATION_AUDIO
*
Favor faithfulness to the original input.
*
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
*
Configure the minimum possible coding delay by disabling certain modes * of operation.
*
* @param[out] error int *: Returns #OPUS_OK on success, or an error * code (see @ref opus_errorcodes) on * failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionEncoder *opus_projection_ambisonics_encoder_create( opus_int32 Fs, int channels, int mapping_family, int *streams, int *coupled_streams, int application, int *error ) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5); /** Initialize a previously allocated projection encoder state. * The memory pointed to by \a st must be at least the size returned by * opus_projection_ambisonics_encoder_get_size(). * This is intended for applications which use their own allocator instead of * malloc. * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. * @see opus_projection_ambisonics_encoder_create * @see opus_projection_ambisonics_encoder_get_size * @param st OpusProjectionEncoder*: Projection encoder state to initialize. * @param Fs opus_int32: Sampling rate of the input signal (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels in the input signal. * This must be at most 255. * It may be greater than the number of * coded channels (streams + * coupled_streams). * @param streams int: The total number of streams to encode from the * input. * This must be no more than the number of channels. * @param coupled_streams int: Number of coupled (2 channel) streams * to encode. * This must be no larger than the total * number of streams. * Additionally, The total number of * encoded channels (streams + * coupled_streams) must be no * more than the number of input channels. * @param application int: The target encoder application. * This must be one of the following: *
*
#OPUS_APPLICATION_VOIP
*
Process signal for improved speech intelligibility.
*
#OPUS_APPLICATION_AUDIO
*
Favor faithfulness to the original input.
*
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
*
Configure the minimum possible coding delay by disabling certain modes * of operation.
*
* @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) * on failure. */ OPUS_EXPORT int opus_projection_ambisonics_encoder_init( OpusProjectionEncoder *st, opus_int32 Fs, int channels, int mapping_family, int *streams, int *coupled_streams, int application ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); /** Encodes a projection Opus frame. * @param st OpusProjectionEncoder*: Projection encoder state. * @param[in] pcm const opus_int16*: The input signal as interleaved * samples. * This must contain * frame_size*channels * samples. * @param frame_size int: Number of samples per channel in the input * signal. * This must be an Opus frame size for the * encoder's sampling rate. * For example, at 48 kHz the permitted values * are 120, 240, 480, 960, 1920, and 2880. * Passing in a duration of less than 10 ms * (480 samples at 48 kHz) will prevent the * encoder from using the LPC or hybrid modes. * @param[out] data unsigned char*: Output payload. * This must contain storage for at * least \a max_data_bytes. * @param [in] max_data_bytes opus_int32: Size of the allocated * memory for the output * payload. This may be * used to impose an upper limit on * the instant bitrate, but should * not be used as the only bitrate * control. Use #OPUS_SET_BITRATE to * control the bitrate. * @returns The length of the encoded packet (in bytes) on success or a * negative error code (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode( OpusProjectionEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Encodes a projection Opus frame from floating point input. * @param st OpusProjectionEncoder*: Projection encoder state. * @param[in] pcm const float*: The input signal as interleaved * samples with a normal range of * +/-1.0. * Samples with a range beyond +/-1.0 * are supported but will be clipped by * decoders using the integer API and * should only be used if it is known * that the far end supports extended * dynamic range. * This must contain * frame_size*channels * samples. * @param frame_size int: Number of samples per channel in the input * signal. * This must be an Opus frame size for the * encoder's sampling rate. * For example, at 48 kHz the permitted values * are 120, 240, 480, 960, 1920, and 2880. * Passing in a duration of less than 10 ms * (480 samples at 48 kHz) will prevent the * encoder from using the LPC or hybrid modes. * @param[out] data unsigned char*: Output payload. * This must contain storage for at * least \a max_data_bytes. * @param [in] max_data_bytes opus_int32: Size of the allocated * memory for the output * payload. This may be * used to impose an upper limit on * the instant bitrate, but should * not be used as the only bitrate * control. Use #OPUS_SET_BITRATE to * control the bitrate. * @returns The length of the encoded packet (in bytes) on success or a * negative error code (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode_float( OpusProjectionEncoder *st, const float *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); /** Frees an OpusProjectionEncoder allocated by * opus_projection_ambisonics_encoder_create(). * @param st OpusProjectionEncoder*: Projection encoder state to be freed. */ OPUS_EXPORT void opus_projection_encoder_destroy(OpusProjectionEncoder *st); /** Perform a CTL function on a projection Opus encoder. * * Generally the request and subsequent arguments are generated by a * convenience macro. * @param st OpusProjectionEncoder*: Projection encoder state. * @param request This and all remaining parameters should be replaced by one * of the convenience macros in @ref opus_genericctls, * @ref opus_encoderctls, @ref opus_multistream_ctls, or * @ref opus_projection_ctls * @see opus_genericctls * @see opus_encoderctls * @see opus_multistream_ctls * @see opus_projection_ctls */ OPUS_EXPORT int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); /**@}*/ /**\name Projection decoder functions */ /**@{*/ /** Gets the size of an OpusProjectionDecoder structure. * @param channels int: The total number of output channels. * This must be no more than 255. * @param streams int: The total number of streams coded in the * input. * This must be no more than 255. * @param coupled_streams int: Number streams to decode as coupled * (2 channel) streams. * This must be no larger than the total * number of streams. * Additionally, The total number of * coded channels (streams + * coupled_streams) must be no * more than 255. * @returns The size in bytes on success, or a negative error code * (see @ref opus_errorcodes) on error. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_decoder_get_size( int channels, int streams, int coupled_streams ); /** Allocates and initializes a projection decoder state. * Call opus_projection_decoder_destroy() to release * this object when finished. * @param Fs opus_int32: Sampling rate to decode at (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels to output. * This must be at most 255. * It may be different from the number of coded * channels (streams + * coupled_streams). * @param streams int: The total number of streams coded in the * input. * This must be no more than 255. * @param coupled_streams int: Number of streams to decode as coupled * (2 channel) streams. * This must be no larger than the total * number of streams. * Additionally, The total number of * coded channels (streams + * coupled_streams) must be no * more than 255. * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix * that mapping from coded channels to output channels, * as described in @ref opus_projection and * @ref opus_projection_ctls. * @param demixing_matrix_size opus_int32: The size in bytes of the * demixing matrix, as * described in @ref * opus_projection_ctls. * @param[out] error int *: Returns #OPUS_OK on success, or an error * code (see @ref opus_errorcodes) on * failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionDecoder *opus_projection_decoder_create( opus_int32 Fs, int channels, int streams, int coupled_streams, unsigned char *demixing_matrix, opus_int32 demixing_matrix_size, int *error ) OPUS_ARG_NONNULL(5); /** Intialize a previously allocated projection decoder state object. * The memory pointed to by \a st must be at least the size returned by * opus_projection_decoder_get_size(). * This is intended for applications which use their own allocator instead of * malloc. * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. * @see opus_projection_decoder_create * @see opus_projection_deocder_get_size * @param st OpusProjectionDecoder*: Projection encoder state to initialize. * @param Fs opus_int32: Sampling rate to decode at (in Hz). * This must be one of 8000, 12000, 16000, * 24000, or 48000. * @param channels int: Number of channels to output. * This must be at most 255. * It may be different from the number of coded * channels (streams + * coupled_streams). * @param streams int: The total number of streams coded in the * input. * This must be no more than 255. * @param coupled_streams int: Number of streams to decode as coupled * (2 channel) streams. * This must be no larger than the total * number of streams. * Additionally, The total number of * coded channels (streams + * coupled_streams) must be no * more than 255. * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix * that mapping from coded channels to output channels, * as described in @ref opus_projection and * @ref opus_projection_ctls. * @param demixing_matrix_size opus_int32: The size in bytes of the * demixing matrix, as * described in @ref * opus_projection_ctls. * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) * on failure. */ OPUS_EXPORT int opus_projection_decoder_init( OpusProjectionDecoder *st, opus_int32 Fs, int channels, int streams, int coupled_streams, unsigned char *demixing_matrix, opus_int32 demixing_matrix_size ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); /** Decode a projection Opus packet. * @param st OpusProjectionDecoder*: Projection decoder state. * @param[in] data const unsigned char*: Input payload. * Use a NULL * pointer to indicate packet * loss. * @param len opus_int32: Number of bytes in payload. * @param[out] pcm opus_int16*: Output signal, with interleaved * samples. * This must contain room for * frame_size*channels * samples. * @param frame_size int: The number of samples per channel of * available space in \a pcm. * If this is less than the maximum packet duration * (120 ms; 5760 for 48kHz), this function will not be capable * of decoding some packets. In the case of PLC (data==NULL) * or FEC (decode_fec=1), then frame_size needs to be exactly * the duration of audio that is missing, otherwise the * decoder will not be in the optimal state to decode the * next incoming packet. For the PLC and FEC cases, frame_size * must be a multiple of 2.5 ms. * @param decode_fec int: Flag (0 or 1) to request that any in-band * forward error correction data be decoded. * If no such data is available, the frame is * decoded as if it were lost. * @returns Number of samples decoded on success or a negative error code * (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode( OpusProjectionDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Decode a projection Opus packet with floating point output. * @param st OpusProjectionDecoder*: Projection decoder state. * @param[in] data const unsigned char*: Input payload. * Use a NULL * pointer to indicate packet * loss. * @param len opus_int32: Number of bytes in payload. * @param[out] pcm opus_int16*: Output signal, with interleaved * samples. * This must contain room for * frame_size*channels * samples. * @param frame_size int: The number of samples per channel of * available space in \a pcm. * If this is less than the maximum packet duration * (120 ms; 5760 for 48kHz), this function will not be capable * of decoding some packets. In the case of PLC (data==NULL) * or FEC (decode_fec=1), then frame_size needs to be exactly * the duration of audio that is missing, otherwise the * decoder will not be in the optimal state to decode the * next incoming packet. For the PLC and FEC cases, frame_size * must be a multiple of 2.5 ms. * @param decode_fec int: Flag (0 or 1) to request that any in-band * forward error correction data be decoded. * If no such data is available, the frame is * decoded as if it were lost. * @returns Number of samples decoded on success or a negative error code * (see @ref opus_errorcodes) on failure. */ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode_float( OpusProjectionDecoder *st, const unsigned char *data, opus_int32 len, float *pcm, int frame_size, int decode_fec ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); /** Perform a CTL function on a projection Opus decoder. * * Generally the request and subsequent arguments are generated by a * convenience macro. * @param st OpusProjectionDecoder*: Projection decoder state. * @param request This and all remaining parameters should be replaced by one * of the convenience macros in @ref opus_genericctls, * @ref opus_decoderctls, @ref opus_multistream_ctls, or * @ref opus_projection_ctls. * @see opus_genericctls * @see opus_decoderctls * @see opus_multistream_ctls * @see opus_projection_ctls */ OPUS_EXPORT int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); /** Frees an OpusProjectionDecoder allocated by * opus_projection_decoder_create(). * @param st OpusProjectionDecoder: Projection decoder state to be freed. */ OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st); /**@}*/ /**@}*/ #ifdef __cplusplus } #endif #endif /* OPUS_PROJECTION_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/libopus/opus_types.h ================================================ /* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */ /* Modified by Jean-Marc Valin */ /* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* opus_types.h based on ogg_types.h from libogg */ /** @file opus_types.h @brief Opus reference implementation types */ #ifndef OPUS_TYPES_H #define OPUS_TYPES_H #define opus_int int /* used for counters etc; at least 16 bits */ #define opus_int64 long long #define opus_int8 signed char #define opus_uint unsigned int /* used for counters etc; at least 16 bits */ #define opus_uint64 unsigned long long #define opus_uint8 unsigned char /* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */ #if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H)) #include # undef opus_int64 # undef opus_int8 # undef opus_uint64 # undef opus_uint8 typedef int8_t opus_int8; typedef uint8_t opus_uint8; typedef int16_t opus_int16; typedef uint16_t opus_uint16; typedef int32_t opus_int32; typedef uint32_t opus_uint32; typedef int64_t opus_int64; typedef uint64_t opus_uint64; #elif defined(_WIN32) # if defined(__CYGWIN__) # include <_G_config.h> typedef _G_int32_t opus_int32; typedef _G_uint32_t opus_uint32; typedef _G_int16 opus_int16; typedef _G_uint16 opus_uint16; # elif defined(__MINGW32__) typedef short opus_int16; typedef unsigned short opus_uint16; typedef int opus_int32; typedef unsigned int opus_uint32; # elif defined(__MWERKS__) typedef int opus_int32; typedef unsigned int opus_uint32; typedef short opus_int16; typedef unsigned short opus_uint16; # else /* MSVC/Borland */ typedef __int32 opus_int32; typedef unsigned __int32 opus_uint32; typedef __int16 opus_int16; typedef unsigned __int16 opus_uint16; # endif #elif defined(__MACOS__) # include typedef SInt16 opus_int16; typedef UInt16 opus_uint16; typedef SInt32 opus_int32; typedef UInt32 opus_uint32; #elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ # include typedef int16_t opus_int16; typedef u_int16_t opus_uint16; typedef int32_t opus_int32; typedef u_int32_t opus_uint32; #elif defined(__BEOS__) /* Be */ # include typedef int16 opus_int16; typedef u_int16 opus_uint16; typedef int32_t opus_int32; typedef u_int32_t opus_uint32; #elif defined (__EMX__) /* OS/2 GCC */ typedef short opus_int16; typedef unsigned short opus_uint16; typedef int opus_int32; typedef unsigned int opus_uint32; #elif defined (DJGPP) /* DJGPP */ typedef short opus_int16; typedef unsigned short opus_uint16; typedef int opus_int32; typedef unsigned int opus_uint32; #elif defined(R5900) /* PS2 EE */ typedef int opus_int32; typedef unsigned opus_uint32; typedef short opus_int16; typedef unsigned short opus_uint16; #elif defined(__SYMBIAN32__) /* Symbian GCC */ typedef signed short opus_int16; typedef unsigned short opus_uint16; typedef signed int opus_int32; typedef unsigned int opus_uint32; #elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) typedef short opus_int16; typedef unsigned short opus_uint16; typedef long opus_int32; typedef unsigned long opus_uint32; #elif defined(CONFIG_TI_C6X) typedef short opus_int16; typedef unsigned short opus_uint16; typedef int opus_int32; typedef unsigned int opus_uint32; #else /* Give up, take a reasonable guess */ typedef short opus_int16; typedef unsigned short opus_uint16; typedef int opus_int32; typedef unsigned int opus_uint32; #endif #endif /* OPUS_TYPES_H */ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/ogg_opus_encoder.cc ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ogg_opus_encoder.h" #include #include #include #include #include #include "opus_tools/opus_header.h" // Ogg Opus information comes from the standard here: // https://tools.ietf.org/html/draft-ietf-codec-oggopus-14 // This was also a useful reference: // https://github.com/krad-radio/butt-krad-opus/blob/302a4b6a6a596be6632f30cfb567a6a6d8fcb3f9/src/opus_encode.cpp namespace audio_util { namespace { static constexpr float kEncodingBufferSizeSeconds = 0.02f; std::string SerializeUint32(uint32_t value) { std::string result(4, '\0'); #if __BYTE_ORDER == __BIG_ENDIAN #error The following line assumes that the byte order is little endian. #endif memcpy(&result[0], &value, sizeof(value)); return result; } } // namespace OggOpusEncoder::OggOpusEncoder(int num_channels, int sample_rate_hz, int bitrate_bps, bool use_vbr, bool low_latency_mode) : num_channels_(num_channels), sample_rate_hz_(sample_rate_hz), frame_size_(kEncodingBufferSizeSeconds * sample_rate_hz_), encoder_(OpusUniquePtr( opus_encoder_create(sample_rate_hz_, num_channels_, OPUS_APPLICATION_AUDIO, &error_code_), opus_encoder_destroy)), flushed_(false), low_latency_mode_(low_latency_mode), elements_in_pcm_frame_(0), pcm_frame_(num_channels_ * frame_size_) { assert(num_channels <= 2); // Only mono and stereo are supported). std::vector valid_sample_rates = {8000, 12000, 16000, 24000, 48000}; assert(std::find(valid_sample_rates.begin(), valid_sample_rates.end(), sample_rate_hz) != valid_sample_rates.end()); assert(bitrate_bps >= 500); assert(bitrate_bps <= 512000); opus_encoder_ctl(encoder_.get(), OPUS_SET_BITRATE(bitrate_bps)); if (!use_vbr) { opus_encoder_ctl(encoder_.get(), OPUS_SET_VBR(0)); } constexpr int kComplexity = 4; opus_encoder_ctl(encoder_.get(), OPUS_SET_COMPLEXITY(kComplexity)); // We will always pass exactly one frame at a time to the encoder. opus_frame_.resize(kBytesPerSample * pcm_frame_.size()); // Start generating Ogg packets (though they don't get sent out until the // first call to Encode()). packet_count_ = 0; granule_position_ = 0; ogg_stream_init(&stream_, 0 /* serial number */); GenerateOggPacketsForHeader(); } OggOpusEncoder::~OggOpusEncoder() { ogg_stream_clear(&stream_); } // Encodes 16-bit PCM data in OggOpus format. const std::vector& OggOpusEncoder::Process( const std::vector& pcm) { assert(!flushed_); assert((pcm.size() % num_channels_) == 0); if (!header_) { ogg_bytes_.resize(0); } else { header_ = false; } int num_samples_processed = 0; // Process the first block, handling any leftovers from a previous round. if (elements_in_pcm_frame_ > 0) { int entries_to_write = std::min(pcm_frame_.size() - elements_in_pcm_frame_, pcm.size()); std::copy(pcm.begin(), pcm.begin() + entries_to_write, pcm_frame_.begin() + elements_in_pcm_frame_); elements_in_pcm_frame_ += entries_to_write; if (elements_in_pcm_frame_ == pcm_frame_.size()) { // pcm_frame_ is full, encode it. int num_opus_frame_bytes = opus_encode(encoder_.get(), pcm_frame_.data(), frame_size_, opus_frame_.data(), opus_frame_.size()); assert(num_opus_frame_bytes >= 0); GenerateOggPacketsForOpusFrame(opus_frame_.data(), num_opus_frame_bytes, &ogg_bytes_, false); num_samples_processed += entries_to_write; pcm_frame_.assign(pcm_frame_.size(), 0); elements_in_pcm_frame_ = 0; } else { // There's nothing to encode. We've put all of pcm data into pcm_frame_ // for later processing. ogg_bytes_.resize(0); return ogg_bytes_; } } // Process whole frames directly from pcm. while (num_samples_processed + frame_size_ * num_channels_ <= pcm.size()) { int num_opus_frame_bytes = opus_encode(encoder_.get(), pcm.data() + num_samples_processed, frame_size_, opus_frame_.data(), opus_frame_.size()); assert(num_opus_frame_bytes >= 0); GenerateOggPacketsForOpusFrame(opus_frame_.data(), num_opus_frame_bytes, &ogg_bytes_, false); num_samples_processed += frame_size_ * num_channels_; } if (low_latency_mode_) { // Force the codec to produce samples for every input buffer. AppendOggStateToBuffer(&ogg_bytes_, true); } // Place any remaining samples in pcm_frame_. elements_in_pcm_frame_ = pcm.size() - num_samples_processed; std::copy(pcm.begin() + num_samples_processed, pcm.end(), pcm_frame_.begin()); return ogg_bytes_; } // Returns any remaining samples from the codec. const std::vector& OggOpusEncoder::Flush() { assert(!flushed_); ogg_bytes_.resize(0); int num_opus_frame_bytes = opus_encode(encoder_.get(), pcm_frame_.data(), frame_size_, opus_frame_.data(), opus_frame_.size()); assert(num_opus_frame_bytes >= 0); GenerateOggPacketsForOpusFrame(opus_frame_.data(), num_opus_frame_bytes, &ogg_bytes_, true); flushed_ = true; return ogg_bytes_; } void OggOpusEncoder::GenerateOggPacketsForHeader() { // Both header packets must have granule position of zero. assert(granule_position_ == 0); OpusHeader header; header.version = 1; header.channels = num_channels_; opus_encoder_ctl(encoder_.get(), OPUS_GET_LOOKAHEAD(&header.preskip)); header.input_sample_rate = sample_rate_hz_; header.gain = 0; header.channel_mapping = 0; // Write the ID header. ogg_packet id_packet; id_packet.b_o_s = 1; // The first packet. id_packet.e_o_s = 0; id_packet.granulepos = granule_position_; id_packet.packetno = packet_count_; constexpr int kHeaderSizeUpperBound = 64; id_packet.packet = new unsigned char[kHeaderSizeUpperBound]; // opus_header_to_packet fills id_packet.packet with header data and returns // the number of bytes. id_packet.bytes = opus_header_to_packet(&header, id_packet.packet, kHeaderSizeUpperBound); // Add the ID packet into the stream. packet_count_++; ogg_stream_packetin(&stream_, &id_packet); // Write the comment header. ogg_packet comment_packet; comment_packet.b_o_s = 0; comment_packet.e_o_s = 0; comment_packet.granulepos = granule_position_; comment_packet.packetno = packet_count_; const std::string kVendor = "Google using libopus"; std::string packet = ""; packet.append("OpusTags"); packet.append(SerializeUint32(kVendor.size())); packet.append(kVendor); packet.append(SerializeUint32(0)); comment_packet.packet = const_cast( reinterpret_cast(packet.c_str())); comment_packet.bytes = packet.length(); // Add the comment header into the stream. packet_count_++; ogg_stream_packetin(&stream_, &comment_packet); // Force a page break after the comment header. // According to // https://tools.ietf.org/html/draft-ietf-codec-oggopus-14#section-3 there is // a mandatory page break after the comment header. AppendOggStateToBuffer(&ogg_bytes_, true); header_ = true; delete[] id_packet.packet; } void OggOpusEncoder::GenerateOggPacketsForOpusFrame( unsigned char* opus_frame_bytes, int opus_bytes_length, std::vector* ogg_bytes, bool flush) { // Flush data from the ogg object into the outgoing stream. AppendOggStateToBuffer(ogg_bytes, flush); // Write the most recent buffer of Opus data into an Ogg packet. ogg_packet frame_packet; frame_packet.b_o_s = 0; frame_packet.e_o_s = flush ? 1 : 0; // According to // https://tools.ietf.org/html/draft-ietf-codec-oggopus-14#section-4 the // granule position should include all samples up to the last packet completed // on the page, so we need to update granule_position_ before assigning it to // the packet. If we're closing the stream, we don't assume that the last // packet includes a full frame. if (flush) { granule_position_ += (elements_in_pcm_frame_ / num_channels_); } else { granule_position_ += frame_size_; } frame_packet.granulepos = granule_position_; frame_packet.packetno = packet_count_; frame_packet.packet = opus_frame_bytes; frame_packet.bytes = opus_bytes_length; // Add the data packet into the stream. packet_count_++; ogg_stream_packetin(&stream_, &frame_packet); // Try flushing again after data packet. AppendOggStateToBuffer(ogg_bytes, flush); } void OggOpusEncoder::AppendOggStateToBuffer(std::vector* buffer, bool flush_ogg_stream) { int (*write_fun)(ogg_stream_state*, ogg_page*) = flush_ogg_stream ? &ogg_stream_flush : &ogg_stream_pageout; while (write_fun(&stream_, &page_) != 0) { const int initial_size = buffer->size(); buffer->resize(buffer->size() + page_.header_len + page_.body_len); memcpy(buffer->data() + initial_size, page_.header, page_.header_len); memcpy(buffer->data() + initial_size + page_.header_len, page_.body, page_.body_len); } } } // namespace audio_util ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/ogg_opus_encoder.h ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef AUDIO_UTIL_OGG_OPUS_ENCODER_H_ #define AUDIO_UTIL_OGG_OPUS_ENCODER_H_ #include #include #include #include #include "libogg/ogg.h" #include "libopus/opus.h" namespace audio_util { // This class is meant to be a dependency-light streaming encoder. // // Encoding is done internally on a block size of 20ms, which seems to be the // recommended size for Opus encoding. class OggOpusEncoder { public: // Input is int16 data. constexpr static int kBytesPerSample = 2; constexpr static int kBitsPerSample = 16; // num_channels must be 1 or 2. // sample rate must be one of {8000, 12000, 16000, 24000, 48000} // Note that low_latency_mode will increase the total number of Ogg packets, // but will reduce overall latency of the codec. This does not impact the // quality of audio compression, only how the data is packaged in the Ogg // container. Low latency mode is only recommended for realtime streaming // applications. See test for actual bitrate increases. OggOpusEncoder(int num_channels, int sample_rate_hz, int bitrate_bps, bool use_vbr, bool low_latency_mode); ~OggOpusEncoder(); // Encodes 16-bit PCM data in OggOpus format. There is no restriction on the // size of the input vector. // Note that it is very common for the returned vector to be empty. Keep // calling this function with new samples until they become available. const std::vector& Process(const std::vector& pcm); // Returns any remaining samples from the codec. This should be called last, // and never more than once. const std::vector& Flush(); private: using OpusUniquePtr = std::unique_ptr; std::string GetOpusErrorMessage() const; // Push the Opus header details into the ogg stream. void GenerateOggPacketsForHeader(); // Push data from a single Opus frame into an Ogg stream. void GenerateOggPacketsForOpusFrame(unsigned char* opus_frame_bytes, int opus_bytes_length, std::vector* ogg_bytes, bool flush); // Moves data from the stream_ object into buffer. void AppendOggStateToBuffer(std::vector* buffer, bool flush); int num_channels_; int sample_rate_hz_; int bitrate_bps_; // Number of samples in an Opus 20ms frame for a single channel. int frame_size_; OpusUniquePtr encoder_; // Stores the status of Opus codec initialization. int error_code_; // Checks that Flush() isn't called multiple times. bool flushed_; bool header_; // When true, flushing of the Ogg stream after every call to Process(). bool low_latency_mode_; // A preallocated buffer to store the temporary OGG result. std::vector opus_frame_; std::vector ogg_bytes_; // A stored buffer for a single frame of PCM data to be processed. int elements_in_pcm_frame_; std::vector pcm_frame_; // Ogg objects. ogg_stream_state stream_; ogg_page page_; int packet_count_; // Count of packets pushed to the stream. int granule_position_; // Position in the ogg stream. }; } // namespace audio_util #endif // AUDIO_UTIL_OGG_OPUS_ENCODER_H_ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/cpp/opus_tools/opus_header.h ================================================ /* Copyright (C)2012 Xiph.Org Foundation File: opus_header.h Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef OPUS_HEADER_H #define OPUS_HEADER_H #include "../libogg/ogg.h" #ifdef __cplusplus extern "C" { #endif typedef struct { int version; int channels; /* Number of channels: 1..255 */ int preskip; ogg_uint32_t input_sample_rate; int gain; /* in dB S7.8 should be zero whenever possible */ int channel_mapping; /* The rest is only used if channel_mapping != 0 */ int nb_streams; int nb_coupled; unsigned char stream_map[255]; } OpusHeader; int opus_header_parse(const unsigned char *header, int len, OpusHeader *h); int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len); extern const int wav_permute_matrix[8][8]; #ifdef __cplusplus } #endif #endif ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java ================================================ package com.teamopensmartglasses.smartglassesmanager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.util.Log; import android.widget.Toast; import androidx.core.app.NotificationCompat; import androidx.lifecycle.LifecycleService; import androidx.preference.PreferenceManager; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.PostGenericGlobalMessageEvent; import com.teamopensmartglasses.augmentoslib.events.SmartGlassesConnectedEvent; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.SmartGlassesFontSize; import com.teamopensmartglasses.smartglassesmanager.comms.MessageTypes; import com.teamopensmartglasses.augmentoslib.events.BulletPointListViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.CenteredTextViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.DoubleTextWallViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.FinalScrollingTextRequestEvent; import com.teamopensmartglasses.augmentoslib.events.HomeScreenEvent; import com.teamopensmartglasses.augmentoslib.events.ReferenceCardImageViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.ReferenceCardSimpleViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.RowsCardViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.SendBitmapViewRequestEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.SetFontSizeEvent; import com.teamopensmartglasses.augmentoslib.events.TextWallViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.ScrollingTextViewStartRequestEvent; import com.teamopensmartglasses.augmentoslib.events.ScrollingTextViewStopRequestEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.SmartGlassesConnectionEvent; import com.teamopensmartglasses.augmentoslib.events.TextLineViewRequestEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.TextToSpeechEvent; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.ASR_FRAMEWORKS; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.SpeechRecSwitchSystem; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.AudioWearable; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.InmoAirOne; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.SmartGlassesDevice; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.SmartGlassesOperatingSystem; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.TCLRayNeoXTwo; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.VuzixShield; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.VuzixUltralite; import com.teamopensmartglasses.smartglassesmanager.texttospeech.TextToSpeechSystem; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBusException; import org.greenrobot.eventbus.Subscribe; import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; import io.reactivex.rxjava3.subjects.PublishSubject; /** Main service of Smart Glasses Manager, that starts connections to smart glasses and talks to third party apps (3PAs) */ public abstract class SmartGlassesAndroidService extends LifecycleService { private static final String TAG = "SGM_ASP_Service"; // Service Binder given to clients private final IBinder binder = new LocalBinder(); public static final String ACTION_START_FOREGROUND_SERVICE = "MY_ACTION_START_FOREGROUND_SERVICE"; public static final String ACTION_STOP_FOREGROUND_SERVICE = "MY_ACTION_STOP_FOREGROUND_SERVICE"; private int myNotificationId; private Class mainActivityClass; private String myChannelId; private String notificationAppName; private String notificationDescription; private int notificationDrawable; //Text to Speech private TextToSpeechSystem textToSpeechSystem; //observables to send data around app PublishSubject dataObservable; //representatives of the other pieces of the system SmartGlassesRepresentative smartGlassesRepresentative; //speech rec SpeechRecSwitchSystem speechRecSwitchSystem; //connection handler public Handler connectHandler; public SmartGlassesAndroidService(Class mainActivityClass, String myChannelId, int myNotificationId, String notificationAppName, String notificationDescription, int notificationDrawable){ this.myNotificationId = myNotificationId; this.mainActivityClass = mainActivityClass; this.myChannelId = myChannelId; this.notificationAppName = notificationAppName; this.notificationDescription = notificationDescription; this.notificationDrawable = notificationDrawable; } @Override public void onCreate() { super.onCreate(); //setup connection handler connectHandler = new Handler(); //start speech rec speechRecSwitchSystem = new SpeechRecSwitchSystem(this.getApplicationContext()); ASR_FRAMEWORKS asrFramework = getChosenAsrFramework(this.getApplicationContext()); String transcribeLanguage = getChosenTranscribeLanguage(this.getApplicationContext()); String targetLanguage = getChosenTargetLanguage(this.getApplicationContext()); String sourceLanguage = getChosenSourceLanguage(this.getApplicationContext()); int selectedLiveCaptionsTranslation = getSelectedLiveCaptionsTranslation(this.getApplicationContext()); if (selectedLiveCaptionsTranslation != 2) speechRecSwitchSystem.startAsrFramework(asrFramework, transcribeLanguage); else { if (transcribeLanguage.equals(sourceLanguage)) speechRecSwitchSystem.startAsrFramework(asrFramework, transcribeLanguage, targetLanguage); // If transcribe language and source language are the same translate to the target language else speechRecSwitchSystem.startAsrFramework(asrFramework, transcribeLanguage, sourceLanguage); } // speechRecSwitchSystem.startAsrFramework(asrFramework, "Chinese (Hanzi)", "English"); //setup data observable which passes information (transcripts, commands, etc. around our app using mutlicasting dataObservable = PublishSubject.create(); //start text to speech textToSpeechSystem = new TextToSpeechSystem(this); textToSpeechSystem.setup(); } protected void setupEventBusSubscribers() { try { EventBus.getDefault().register(this); } catch(EventBusException e){ e.printStackTrace(); } } @Subscribe public void handleConnectionEvent(SmartGlassesConnectionEvent event) { sendUiUpdate(); } protected abstract void onGlassesConnected(SmartGlassesDevice device); public void connectToSmartGlasses(SmartGlassesDevice device) { //this represents the smart glasses - it handles the connection, sending data to them, etc LifecycleService currContext = this; connectHandler.post(new Runnable() { @Override public void run() { Log.d(TAG, "CONNECTING TO SMART GLASSES"); smartGlassesRepresentative = new SmartGlassesRepresentative(currContext, device, currContext, dataObservable); smartGlassesRepresentative.connectToSmartGlasses(); } }); } @Override public void onDestroy() { Log.d(TAG, "WearableAiAspService killing itself and all its children"); EventBus.getDefault().unregister(this); //kill speech rec if (speechRecSwitchSystem != null){ speechRecSwitchSystem.destroy(); } //kill asg connection if (smartGlassesRepresentative != null) { smartGlassesRepresentative.destroy(); smartGlassesRepresentative = null; } //kill data transmitters if (dataObservable != null) { dataObservable.onComplete(); } //kill textToSpeech textToSpeechSystem.destroy(); //kill aioConnect aioRetryHandler.removeCallbacks(aioRetryConnectionTask); //call parent destroy super.onDestroy(); Log.d(TAG, "WearableAiAspService destroy complete"); } public void sendTestCard(String title, String body, String img) { Log.d(TAG, "SENDING TEST CARD FROM WAIService"); EventBus.getDefault().post(new ReferenceCardSimpleViewRequestEvent(title, body)); } public int getSmartGlassesConnectState() { if (smartGlassesRepresentative != null) { return smartGlassesRepresentative.getConnectionState(); } else { return 0; } } public SmartGlassesDevice getConnectedSmartGlasses() { if (smartGlassesRepresentative == null) return null; if(smartGlassesRepresentative.getConnectionState() != 2) return null; return smartGlassesRepresentative.smartGlassesDevice; } public SmartGlassesOperatingSystem getConnectedDeviceModelOs(){ if (smartGlassesRepresentative == null) return null; if(smartGlassesRepresentative.getConnectionState() != 2) return null; return smartGlassesRepresentative.smartGlassesDevice.glassesOs; } public void sendUiUpdate() { //connectionState = 2 means connected Intent intent = new Intent(); intent.setAction(MessageTypes.GLASSES_STATUS_UPDATE); // Set the optional additional information in extra field. int connectionState; if (smartGlassesRepresentative != null) { connectionState = smartGlassesRepresentative.getConnectionState(); intent.putExtra(MessageTypes.CONNECTION_GLASSES_GLASSES_OBJECT, smartGlassesRepresentative.smartGlassesDevice); // Update preferred wearable if connected if(connectionState == 2){ savePreferredWearable(this, smartGlassesRepresentative.smartGlassesDevice.deviceModelName); onGlassesConnected(smartGlassesRepresentative.smartGlassesDevice); EventBus.getDefault().post(new SmartGlassesConnectedEvent(smartGlassesRepresentative.smartGlassesDevice)); } } else { connectionState = 0; } intent.putExtra(MessageTypes.CONNECTION_GLASSES_STATUS_UPDATE, connectionState); sendBroadcast(intent); } /** Saves the chosen ASR framework in user shared preference. */ public static void saveChosenAsrFramework(Context context, ASR_FRAMEWORKS asrFramework) { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(context.getResources().getString(R.string.SHARED_PREF_ASR_KEY), asrFramework.name()) .apply(); } /** Gets the chosen ASR framework from shared preference. */ public static ASR_FRAMEWORKS getChosenAsrFramework(Context context) { String asrString = PreferenceManager.getDefaultSharedPreferences(context).getString(context.getResources().getString(R.string.SHARED_PREF_ASR_KEY), ""); if (asrString.equals("")){ saveChosenAsrFramework(context, ASR_FRAMEWORKS.AZURE_ASR_FRAMEWORK); asrString = ASR_FRAMEWORKS.AZURE_ASR_FRAMEWORK.name(); } return ASR_FRAMEWORKS.valueOf(asrString); } public void changeChosenAsrFramework(ASR_FRAMEWORKS asrFramework){ saveChosenAsrFramework(getApplicationContext(), asrFramework); if (speechRecSwitchSystem != null) { speechRecSwitchSystem.startAsrFramework(asrFramework); } } /** Gets the API key from shared preference. */ public static String getApiKey(Context context) { return PreferenceManager.getDefaultSharedPreferences(context).getString(context.getResources().getString(R.string.SHARED_PREF_KEY), ""); } /** Saves the API Key in user shared preference. */ public static void saveApiKey(Context context, String key) { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(context.getResources().getString(R.string.SHARED_PREF_KEY), key) .apply(); } /** Gets the preferred wearable from shared preference. */ public static String getPreferredWearable(Context context) { return PreferenceManager.getDefaultSharedPreferences(context).getString(context.getResources().getString(R.string.PREFERRED_WEARABLE), ""); } /** Saves the preferred wearable in user shared preference. */ public static void savePreferredWearable(Context context, String wearableName) { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(context.getResources().getString(R.string.PREFERRED_WEARABLE), wearableName) .apply(); } public static void saveChosenTranscribeLanguage(Context context, String transcribeLanguageString) { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(context.getResources().getString(R.string.SHARED_PREF_TRANSCRIBE_LANGUAGE), transcribeLanguageString) .apply(); } public static String getChosenTranscribeLanguage(Context context) { String transcribeLanguageString = PreferenceManager.getDefaultSharedPreferences(context).getString(context.getResources().getString(R.string.SHARED_PREF_TRANSCRIBE_LANGUAGE), ""); if (transcribeLanguageString.equals("")){ saveChosenTranscribeLanguage(context, "English"); transcribeLanguageString = "English"; } return transcribeLanguageString; } public static void saveChosenTargetLanguage(Context context, String targetLanguageString) { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(context.getResources().getString(R.string.SHARED_PREF_TARGET_LANGUAGE), targetLanguageString) .apply(); } public static String getChosenTargetLanguage(Context context) { String targetLanguageString = PreferenceManager.getDefaultSharedPreferences(context).getString(context.getResources().getString(R.string.SHARED_PREF_TARGET_LANGUAGE), ""); if (targetLanguageString.equals("")){ saveChosenTargetLanguage(context, "English"); targetLanguageString = "English"; } return targetLanguageString; } public static void saveChosenSourceLanguage(Context context, String sourceLanguageString) { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(context.getResources().getString(R.string.SHARED_PREF_SOURCE_LANGUAGE), sourceLanguageString) .apply(); } public static String getChosenSourceLanguage(Context context) { String sourceLanguageString = PreferenceManager.getDefaultSharedPreferences(context).getString(context.getResources().getString(R.string.SHARED_PREF_SOURCE_LANGUAGE), ""); if (sourceLanguageString.equals("")){ saveChosenSourceLanguage(context, "English"); sourceLanguageString = "English"; } return sourceLanguageString; } public static void saveSelectedLiveCaptionsTranslationChecked(Context context, int liveCaptionsTranslationSelected) { PreferenceManager.getDefaultSharedPreferences(context) .edit() .putInt(context.getResources().getString(R.string.SHARED_PREF_LIVE_CAPTIONS_TRANSLATION), liveCaptionsTranslationSelected) .apply(); } public static int getSelectedLiveCaptionsTranslation(Context context) { return PreferenceManager.getDefaultSharedPreferences(context).getInt(context.getResources().getString(R.string.SHARED_PREF_LIVE_CAPTIONS_TRANSLATION), 0); } //switches the currently running transcribe language without changing the default/saved language public void switchRunningTranscribeLanguage(String language){ if (speechRecSwitchSystem.currentLanguage.equals(language)){ return; } //kill previous speech rec speechRecSwitchSystem.destroy(); speechRecSwitchSystem = null; //start speech rec after small delay Handler speechRecHandler = new Handler(); Context context = this; speechRecHandler.postDelayed(new Runnable() { @Override public void run() { speechRecSwitchSystem = new SpeechRecSwitchSystem(context); ASR_FRAMEWORKS asrFramework = getChosenAsrFramework(context); speechRecSwitchSystem.startAsrFramework(asrFramework, language); } }, 250); } //service stuff private Notification updateNotification() { Context context = getApplicationContext(); PendingIntent action = PendingIntent.getActivity(context, 0, new Intent(context, mainActivityClass), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE); // Flag indicating that if the described PendingIntent already exists, the current one should be canceled before generating a new one. NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); NotificationCompat.Builder builder; String CHANNEL_ID = myChannelId; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, notificationAppName, NotificationManager.IMPORTANCE_HIGH); channel.setDescription(notificationDescription); manager.createNotificationChannel(channel); builder = new NotificationCompat.Builder(this, CHANNEL_ID); return builder.setContentIntent(action) .setContentTitle(notificationAppName) .setContentText(notificationDescription) .setSmallIcon(notificationDrawable) .setTicker("...") .setContentIntent(action) .setOngoing(true).build(); } public class LocalBinder extends Binder { public SmartGlassesAndroidService getService() { // Return this instance of LocalService so clients can call public methods return SmartGlassesAndroidService.this; } } @Override public IBinder onBind(Intent intent) { super.onBind(intent); return binder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); if (intent != null) { String action = intent.getAction(); Bundle extras = intent.getExtras(); switch (action) { case ACTION_START_FOREGROUND_SERVICE: // start the service in the foreground Log.d("TEST", "starting foreground"); startForeground(myNotificationId, updateNotification()); break; case ACTION_STOP_FOREGROUND_SERVICE: stopForeground(true); stopSelf(); break; } } return Service.START_STICKY; } // Setup for aioConnectSmartGlasses ArrayList smartGlassesDevices = new ArrayList<>(); Handler aioRetryHandler = new Handler(); Runnable aioRetryConnectionTask = new Runnable() { @Override public void run() { if (smartGlassesRepresentative == null || smartGlassesRepresentative.getConnectionState() != 2) { // If still disconnected if(!smartGlassesDevices.isEmpty()){ Toast.makeText(getApplicationContext(), "Searching for glasses...", Toast.LENGTH_LONG).show(); // EventBus.getDefault().post(new PostGenericGlobalMessageEvent("Searching for glasses...")); Log.d(TAG, "TRYING TO CONNECT TO: " + smartGlassesDevices.get(0).deviceModelName); if (smartGlassesRepresentative != null) { smartGlassesRepresentative.destroy(); smartGlassesRepresentative = null; } connectToSmartGlasses(smartGlassesDevices.get(0)); smartGlassesDevices.add(smartGlassesDevices.remove(0)); aioRetryHandler.postDelayed(this, 5000); // Schedule another retry if needed } else { aioRetryHandler.removeCallbacks(this); Toast.makeText(getApplicationContext(), "No glasses found", Toast.LENGTH_LONG).show(); // EventBus.getDefault().post(new PostGenericGlobalMessageEvent("No glasses found")); } } else { Toast.makeText(getApplicationContext(), "Connected to " + smartGlassesRepresentative.smartGlassesDevice.deviceModelName, Toast.LENGTH_LONG).show(); // EventBus.getDefault().post(new PostGenericGlobalMessageEvent("Connected to " + smartGlassesRepresentative.smartGlassesDevice.deviceModelName)); } } }; public void aioConnectSmartGlasses(){ if (getChosenAsrFramework(this) == ASR_FRAMEWORKS.GOOGLE_ASR_FRAMEWORK) { String apiKey = getApiKey(getApplicationContext()); if (apiKey == null || apiKey.equals("")) { showNoGoogleAsrDialog(); return; } } String preferred = getPreferredWearable(this.getApplicationContext()); smartGlassesDevices = new ArrayList(Arrays.asList(new VuzixUltralite(), new VuzixShield(), new InmoAirOne(), new TCLRayNeoXTwo())); for (int i = 0; i < smartGlassesDevices.size(); i++){ if (smartGlassesDevices.get(i).deviceModelName.equals(preferred)){ // Move to start for earliest search priority smartGlassesDevices.add(0, smartGlassesDevices.remove(i)); break; } } // Check for Audio Wearable if (preferred.equals(new AudioWearable().deviceModelName)) smartGlassesDevices.add(0, new AudioWearable()); //start loop aioRetryConnectionTask.run(); } public void showNoGoogleAsrDialog(){ new android.app.AlertDialog.Builder(getApplicationContext()).setIcon(android.R.drawable.ic_dialog_alert) .setTitle("No Google API Key Provided") .setMessage("You have Google ASR enabled without an API key. Please turn off Google ASR or enter a valid API key.") .setPositiveButton("Ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }).show(); } //show a reference card on the smart glasses with title and body text public static void sendReferenceCard(String title, String body) { EventBus.getDefault().post(new ReferenceCardSimpleViewRequestEvent(title, body)); } //show a text wall card on the smart glasses public static void sendTextWall(String text) { EventBus.getDefault().post(new TextWallViewRequestEvent(text)); } //show a double text wall card on the smart glasses public static void sendDoubleTextWall(String textTop, String textBottom) { EventBus.getDefault().post(new DoubleTextWallViewRequestEvent(textTop, textBottom)); } //show a reference card on the smart glasses with title and body text public static void sendRowsCard(String[] rowStrings) { EventBus.getDefault().post(new RowsCardViewRequestEvent(rowStrings)); } //show a bullet point list card on the smart glasses with title and bullet points public void sendBulletPointList(String title, String [] bullets) { EventBus.getDefault().post(new BulletPointListViewRequestEvent(title, bullets)); } //show a list of up to 4 rows of text. Only put a few characters per line! public void sendBulletPointList(String[] rowStrings) { EventBus.getDefault().post(new RowsCardViewRequestEvent(rowStrings)); } public void sendReferenceCard(String title, String body, String imgUrl) { EventBus.getDefault().post(new ReferenceCardImageViewRequestEvent(title, body, imgUrl)); } public void sendBitmap(Bitmap bitmap) { EventBus.getDefault().post(new SendBitmapViewRequestEvent(bitmap)); } public void startScrollingText(String title){ EventBus.getDefault().post(new ScrollingTextViewStartRequestEvent(title)); } public void pushScrollingText(String text){ EventBus.getDefault().post(new FinalScrollingTextRequestEvent(text)); } public void stopScrollingText(){ EventBus.getDefault().post(new ScrollingTextViewStopRequestEvent()); } public void sendTextLine(String text) { EventBus.getDefault().post(new TextLineViewRequestEvent(text)); } public void sendTextToSpeech(String text, String languageString) { EventBus.getDefault().post(new TextToSpeechEvent(text, languageString)); } public void sendCenteredText(String text){ EventBus.getDefault().post(new CenteredTextViewRequestEvent(text)); } public void sendCustomContent(String json){ EventBus.getDefault().post(new CenteredTextViewRequestEvent(json)); } public void sendHomeScreen(){ EventBus.getDefault().post(new HomeScreenEvent()); } public void setFontSize(SmartGlassesFontSize fontSize) { EventBus.getDefault().post(new SetFontSizeEvent(fontSize)); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesRepresentative.java ================================================ package com.teamopensmartglasses.smartglassesmanager; import android.content.Context; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.json.JSONObject; import android.os.Handler; import android.util.Log; //custom, our code import androidx.lifecycle.LifecycleOwner; import com.teamopensmartglasses.augmentoslib.events.AudioChunkNewEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.DisableBleScoAudioEvent; import com.teamopensmartglasses.augmentoslib.events.DisplayCustomContentRequestEvent; import com.teamopensmartglasses.augmentoslib.events.DoubleTextWallViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.HomeScreenEvent; import com.teamopensmartglasses.augmentoslib.events.SendBitmapViewRequestEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.SetFontSizeEvent; import com.teamopensmartglasses.augmentoslib.events.TextWallViewRequestEvent; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.AudioWearableSGC; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.UltraliteSGC; import com.teamopensmartglasses.augmentoslib.events.BulletPointListViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.CenteredTextViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.FinalScrollingTextRequestEvent; import com.teamopensmartglasses.augmentoslib.events.IntermediateScrollingTextRequestEvent; import com.teamopensmartglasses.augmentoslib.events.ReferenceCardImageViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.ReferenceCardSimpleViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.RowsCardViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.PromptViewRequestEvent; import com.teamopensmartglasses.augmentoslib.events.ScrollingTextViewStartRequestEvent; import com.teamopensmartglasses.augmentoslib.events.ScrollingTextViewStopRequestEvent; import com.teamopensmartglasses.smartglassesmanager.hci.AudioChunkCallback; import com.teamopensmartglasses.smartglassesmanager.hci.MicrophoneLocalAndBluetooth; //import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.ActiveLookSGC; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.AndroidSGC; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.SmartGlassesCommunicator; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.SmartGlassesDevice; import com.teamopensmartglasses.augmentoslib.events.TextLineViewRequestEvent; //rxjava import java.nio.ByteBuffer; import io.reactivex.rxjava3.subjects.PublishSubject; class SmartGlassesRepresentative { private static final String TAG = "WearableAi_ASGRepresentative"; //receive/send data stream PublishSubject dataObservable; Context context; public SmartGlassesDevice smartGlassesDevice; SmartGlassesCommunicator smartGlassesCommunicator; MicrophoneLocalAndBluetooth bluetoothAudio; //timing settings long referenceCardDelayTime = 10000; LifecycleOwner lifecycleOwner; //handler to handle delayed UI events Handler uiHandler; Handler micHandler; SmartGlassesRepresentative(Context context, SmartGlassesDevice smartGlassesDevice, LifecycleOwner lifecycleOwner, PublishSubject dataObservable){ this.context = context; this.smartGlassesDevice = smartGlassesDevice; this.lifecycleOwner = lifecycleOwner; //receive/send data this.dataObservable = dataObservable; uiHandler = new Handler(); micHandler = new Handler(); //register event bus subscribers EventBus.getDefault().register(this); } public void connectToSmartGlasses(){ switch (smartGlassesDevice.getGlassesOs()){ case ANDROID_OS_GLASSES: smartGlassesCommunicator = new AndroidSGC(context, dataObservable); break; // case ACTIVELOOK_OS_GLASSES: // smartGlassesCommunicator = new ActiveLookSGC(context); // break; case AUDIO_WEARABLE_GLASSES: smartGlassesCommunicator = new AudioWearableSGC(context); break; case ULTRALITE_MCU_OS_GLASSES: smartGlassesCommunicator = new UltraliteSGC(context, lifecycleOwner); break; case EVENREALITIES_G1_MCU_OS_GLASSES: // smartGlassesCommunicator = new EvenRealitiesG1SGC(context, lifecycleOwner); break; } smartGlassesCommunicator.connectToSmartGlasses(); //if the glasses don't support a microphone, this Representative handles local microphone if (smartGlassesDevice.useScoMic) { connectAndStreamLocalMicrophone(true); } else if (!smartGlassesDevice.getHasInMic() && !smartGlassesDevice.getHasOutMic()) { connectAndStreamLocalMicrophone(false); } } @Subscribe public void onDisableBleScoEvent(DisableBleScoAudioEvent receivedEvent) { Log.d(TAG, "onDisableBleScoEvent called"); restartAudioWithNoBleSco(); } public void restartAudioWithNoBleSco(){ //kill current audio if (bluetoothAudio != null) { bluetoothAudio.destroy(); } //start new audio, with no bluetooth connectAndStreamLocalMicrophone(false); } private void connectAndStreamLocalMicrophone(boolean useBluetoothSco){ //follow this order for speed //start audio from bluetooth headset uiHandler.post(new Runnable() { @Override public void run() { bluetoothAudio = new MicrophoneLocalAndBluetooth(context, useBluetoothSco, new AudioChunkCallback(){ @Override public void onSuccess(ByteBuffer chunk){ receiveChunk(chunk); } }); } }); } private void receiveChunk(ByteBuffer chunk){ byte[] audio_bytes = chunk.array(); //throw off new audio chunk event EventBus.getDefault().post(new AudioChunkNewEvent(audio_bytes)); } public void destroy(){ Log.d(TAG, "SG rep destroying"); EventBus.getDefault().unregister(this); if (bluetoothAudio != null) { bluetoothAudio.destroy(); } if (smartGlassesCommunicator != null){ smartGlassesCommunicator.destroy(); smartGlassesCommunicator = null; } Log.d(TAG, "SG rep destroy complete"); } //are our smart glasses currently connected? public int getConnectionState(){ if (smartGlassesCommunicator == null){ return 0; } else { return smartGlassesCommunicator.getConnectionState(); } } public void showReferenceCard(String title, String body){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayReferenceCardSimple(title, body); } } public void showRowsCard(String[] rowStrings){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayRowsCard(rowStrings); } } public void startScrollingTextViewModeTest(){ //pass for now if (smartGlassesCommunicator != null) { smartGlassesCommunicator.startScrollingTextViewMode("ScrollingTextView"); smartGlassesCommunicator.scrollingTextViewFinalText("test line 1"); smartGlassesCommunicator.scrollingTextViewFinalText("line 2 testy boi"); smartGlassesCommunicator.scrollingTextViewFinalText("how's this?"); smartGlassesCommunicator.scrollingTextViewFinalText("this is a line of text that is going to be long enough to wrap around, it would be good to see if it doesn so, that would be super cool"); smartGlassesCommunicator.scrollingTextViewFinalText("test line n"); smartGlassesCommunicator.scrollingTextViewFinalText("line n + 1 testy boi"); smartGlassesCommunicator.scrollingTextViewFinalText("seconnndd how's this?"); } } private void homeUiAfterDelay(long delayTime){ uiHandler.postDelayed(new Runnable() { @Override public void run() { homeScreen(); } }, delayTime); } public void homeScreen(){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.showHomeScreen(); } } @Subscribe public void onHomeScreenEvent(HomeScreenEvent receivedEvent){ homeScreen(); } @Subscribe public void onTextWallViewEvent(TextWallViewRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayTextWall(receivedEvent.text); } } @Subscribe public void onDoubleTextWallViewEvent(DoubleTextWallViewRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayDoubleTextWall(receivedEvent.textTop, receivedEvent.textBottom); } } @Subscribe public void onReferenceCardSimpleViewEvent(ReferenceCardSimpleViewRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayReferenceCardSimple(receivedEvent.title, receivedEvent.body); // homeUiAfterDelay(referenceCardDelayTime); } } @Subscribe public void onRowsCardViewEvent(RowsCardViewRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayRowsCard(receivedEvent.rowStrings); // homeUiAfterDelay(referenceCardDelayTime); } } @Subscribe public void onBulletPointListViewEvent(BulletPointListViewRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayBulletList(receivedEvent.title, receivedEvent.bullets); // homeUiAfterDelay(referenceCardDelayTime); } } @Subscribe public void onReferenceCardImageViewEvent(ReferenceCardImageViewRequestEvent receivedEvent){ Log.d(TAG, "sending reference card image view event"); if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayReferenceCardImage(receivedEvent.title, receivedEvent.body, receivedEvent.imgUrl); // homeUiAfterDelay(referenceCardDelayTime); } } @Subscribe public void onSendBitmapViewRequestEvent(SendBitmapViewRequestEvent receievedEvent){ Log.d(TAG, "Sending a bitmap event"); if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayBitmap(receievedEvent.bmp); } } @Subscribe public void onDisplayCustomContentRequestEvent(DisplayCustomContentRequestEvent receivedEvent){ Log.d(TAG, "Got display custom content event: " + receivedEvent.json); if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayCustomContent(receivedEvent.json); } } @Subscribe public void onTextLineViewRequestEvent(TextLineViewRequestEvent receivedEvent){ Log.d(TAG, "Got text line event: " + receivedEvent.text); if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayTextLine(receivedEvent.text); } } @Subscribe public void onDisplayCenteredTextRequestEvent(CenteredTextViewRequestEvent receivedEvent){ if(smartGlassesCommunicator != null){ smartGlassesCommunicator.displayCenteredText(receivedEvent.text); } } @Subscribe public void onStartScrollingTextViewEvent(ScrollingTextViewStartRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.startScrollingTextViewMode(receivedEvent.title); } } @Subscribe public void onStopScrollingTextViewEvent(ScrollingTextViewStopRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { smartGlassesCommunicator.stopScrollingTextViewMode(); } } @Subscribe public void onFinalScrollingTextEvent(FinalScrollingTextRequestEvent receivedEvent) { Log.d(TAG, "onFinalScrollingTextEvent"); if (smartGlassesCommunicator != null) { smartGlassesCommunicator.scrollingTextViewFinalText(receivedEvent.text); } } @Subscribe public void onIntermediateScrollingTextEvent(IntermediateScrollingTextRequestEvent receivedEvent) { if (smartGlassesCommunicator != null) { smartGlassesCommunicator.scrollingTextViewIntermediateText(receivedEvent.text); } } @Subscribe public void onPromptViewRequestEvent(PromptViewRequestEvent receivedEvent) { Log.d(TAG, "onPromptViewRequestEvent called"); if (smartGlassesCommunicator != null) { smartGlassesCommunicator.displayPromptView(receivedEvent.prompt, receivedEvent.options); } } @Subscribe public void onSetFontSizeEvent(SetFontSizeEvent receivedEvent) { if (smartGlassesCommunicator != null) { smartGlassesCommunicator.setFontSize(receivedEvent.fontSize); } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/comms/AspWebsocketServer.java ================================================ package com.teamopensmartglasses.smartglassesmanager.comms; import java.net.InetSocketAddress; import org.java_websocket.WebSocket; import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; import java.util.Map; import java.util.UUID; import java.nio.ByteBuffer; import java.util.concurrent.ConcurrentHashMap; import android.util.Log; import org.json.JSONObject; import org.json.JSONException; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.subjects.PublishSubject; public class AspWebsocketServer extends WebSocketServer { //data observable we can send data through private static PublishSubject dataObservable; private static Disposable dataSub; private static int connected = 0; private final String TAG = "WearableAI_AspWebsocketServer"; private Map clients = new ConcurrentHashMap<>(); private WebSocket asgConn; public AspWebsocketServer(int port) { super(new InetSocketAddress(port)); setReuseAddr(true); } public AspWebsocketServer(InetSocketAddress address) { super(address); } @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { connected = 2; String uniqueID = UUID.randomUUID().toString(); Log.d(TAG, "Got new connection at address: " + conn.getRemoteSocketAddress()); clients.put(uniqueID, conn); asgConn = conn; } @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { Log.d(TAG, "onClose called"); Log.d(TAG, "WebSocketServer currently has this many conns: " + clients.size()); for (WebSocket connToCheck : clients.values()){ // there was a race condition where the ASG would recconnect before the socket was marked as closed, so the ASP thought their was no connection (connected = 1), but there actually was a connection, so now we check to see if any of the sockets in the clients hashmap is still open Log.d(TAG, "Checking conn with remoteSocketAddress: " + connToCheck.getRemoteSocketAddress()); if (connToCheck.isOpen()){ //exit, because we have a live connection to the ASG Log.d(TAG, "onClose found open connection, so not setting connected=1"); return; } } Log.d(TAG, "onClose set connected=1"); clients.clear(); connected = 1; } @Override public void onMessage(WebSocket conn, String message) { try { // Log.d(TAG, message); JSONObject json_obj = new JSONObject(message); dataObservable.onNext(json_obj); } catch (JSONException e){ //if we send a string, this will get thrown, all messages should be JSON or byte [] // e.printStackTrace(); } } @Override public void onMessage(WebSocket conn, ByteBuffer message) { Log.d(TAG, "GOT MESSAGE BYTES"); } @Override public void onError(WebSocket conn, Exception ex) { ex.printStackTrace(); if (conn != null) { // some errors like port binding failed may not be assignable to a specific websocket } } @Override public void onStart() { //LogHelper.e(TAG, "Server started!"); connected = 1; setConnectionLostTimeout(2); startConnectionLostTimer(); } public void sendJson(JSONObject data){ if (connected == 2){ try { asgConn.send(data.toString()); } catch (WebsocketNotConnectedException e){ e.printStackTrace(); Log.d(TAG, "Not connected, close should be running now."); } } else { Log.d(TAG, "CANNOT SEND JSON, NOT CONNECTED"); } } //receive observable to send and receive data public void setObservable(PublishSubject observable){ Log.d(TAG, "called setObservable"); dataObservable = observable; dataSub = dataObservable.subscribe(i -> handleDataStream(i)); } //this receives data from the data observable. For now, this class decides what to send and what not to send to the ASG private void handleDataStream(JSONObject data){ //first check if it's a type we should handle try{ String type = data.getString(MessageTypes.MESSAGE_TYPE_LOCAL); // if (type.equals(MessageTypes.INTERMEDIATE_TRANSCRIPT)){ //// Log.d(TAG, "AspWebsocketServer got INTERMEDIATE_TRANSCRIPT, sending to ASG"); // //data.put(MessageTypes.MESSAGE_TYPE_LOCAL, data.getString(MessageTypes.MESSAGE_TYPE_ASG)); //change the type to the type for ASG // //data.remove(MessageTypes.MESSAGE_TYPE_ASG); // sendJson(data); // } else if (type.equals(MessageTypes.FINAL_TRANSCRIPT)){ //// Log.d(TAG, "AspWebsocketServer got FINAL_TRANSCRIPT, sending to ASG"); // sendJson(data); if (type.equals(MessageTypes.VOICE_COMMAND_RESPONSE)){ // Log.d(TAG, "AspWebsocketServer got VOICE_COMMAND_RESPONSE, sending to ASG"); sendJson(data); } else if (type.equals(MessageTypes.REFERENCE_CARD_SIMPLE_VIEW)){ // Log.d(TAG, "AspWebsocketServer got REFERENCE_CARD_SIMPLE_VIEW, sending to ASG"); sendJson(data); } else if (type.equals(MessageTypes.REFERENCE_CARD_TEXT_WALL_VIEW)){ // Log.d(TAG, "AspWebsocketServer got REFERENCE_CARD_SIMPLE_VIEW, sending to ASG"); sendJson(data); } else if (type.equals(MessageTypes.REFERENCE_CARD_IMAGE_VIEW)){ sendJson(data); } else if (type.equals(MessageTypes.ACTION_SWITCH_MODES)){ // Log.d(TAG, "AspWebsocketServer got ACTION_SWITCH_MODES, sending to ASG"); sendJson(data); } else if (type.equals(MessageTypes.SCROLLING_TEXT_VIEW_START)){ // Log.d(TAG, "AspWebsocketServer sending STV start"); sendJson(data); } else if (type.equals(MessageTypes.SCROLLING_TEXT_VIEW_STOP)){ // Log.d(TAG, "AspWebsocketServer sending STV start"); sendJson(data); } else if (type.equals(MessageTypes.SCROLLING_TEXT_VIEW_FINAL)){ // Log.d(TAG, "AspWebsocketServer sending STV final"); sendJson(data); } else if (type.equals(MessageTypes.SCROLLING_TEXT_VIEW_INTERMEDIATE)){ // Log.d(TAG, "AspWebsocketServer sending STV intermediate"); sendJson(data); } else if (type.equals(MessageTypes.VOICE_COMMAND_STREAM_EVENT)) { // Log.d(TAG, "AspWebsocketServer got VOICE_COMMAND_STREAM_EVENT, sending to ASG"); sendJson(data); } } catch (JSONException e){ e.printStackTrace(); } } //need to call this so if we get "Force Stop"ped, we will clean up sockets so we can connect on restart public void destroy(){ Log.d(TAG, "destroying"); connected = 0; dataSub.dispose(); dataSub = null; try{ stop(400); } catch (InterruptedException e){ e.printStackTrace(); } Log.d(TAG, "destroy complete"); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/comms/AudioSystem.java ================================================ package com.teamopensmartglasses.smartglassesmanager.comms; import android.media.AudioFormat; import android.util.Base64; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.subjects.PublishSubject; import java.io.IOException; import org.greenrobot.eventbus.EventBus; import org.json.JSONObject; import org.json.JSONException; import android.content.Context; import java.util.Random; import java.nio.ByteOrder; import android.os.Handler; import android.os.HandlerThread; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.io.DataInputStream; import java.net.ServerSocket; import java.net.Socket; import com.teamopensmartglasses.augmentoslib.events.AudioChunkNewEvent; import com.teamopensmartglasses.smartglassesmanager.utils.AES; import android.util.Log; import com.teamopensmartglasses.smartglassesmanager.R; public class AudioSystem { private static String TAG = "WearableAi_AudioSystem"; private boolean shouldDie; private String secretKey; // the audio recording options - same on ASG private static final int RECORDING_RATE = 16000; private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO; private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; //Thread to receive audio from ASG //socket info static int PORT = 4449; private static int mConnectState = 0; final byte [] ack_id = {0x13, 0x37}; final byte [] heart_beat_id = {0x19, 0x20}; final byte [] img_id = {0x01, 0x10}; //id for images //handle heart beat stuff private static long lastHeartbeatTime; private static int heartbeatInterval = 3000; //milliseconds private static int heartbeatPanicX = 3; // number of intervals before we reset connection static Thread HeartbeatThread = null; private int outbound_heart_beats = 0; //socket data static Thread SocketThread = null; static Thread ReceiveThread = null; static Thread SendThread = null; //i/o private DataOutputStream output; private DataInputStream input; //our actual socket connection object ServerSocket serverSocket; private static Socket socket; //send audio to to other services in the app PublishSubject dataObservable; Disposable dataSubscriber; Context context; public AudioSystem(Context context, PublishSubject dataObservable){ this.context = context; //set the key for encryption secretKey = context.getResources().getString(R.string.key); this.dataObservable = dataObservable; dataSubscriber = dataObservable.subscribe(i -> handleDataStream(i)); } //send_queue of data to send through the socket private BlockingQueue send_queue; public void startAudio(){ //make a new queue to hold data to send send_queue = new ArrayBlockingQueue(50); //start the socket thread which will send the raw audio data startSocket(); } public void startSocket(){ //start first socketThread if (socket == null) { mConnectState = 1; SocketThread = new Thread(new SocketThread()); SocketThread.start(); //setup handler to handle keeping connection alive, all subsequent start of SocketThread //start a new handler thread to send heartbeats HandlerThread thread = new HandlerThread("HeartBeater"); thread.start(); Handler heart_beat_handler = new Handler(thread.getLooper()); final int hb_delay = 3000; final int min_hb_delay = 1000; final int max_hb_delay = 2000; Random rand = new Random(); heart_beat_handler.postDelayed(new Runnable() { public void run() { heartBeat(); //random hb_delay for heart beat so as to disallow synchronized failure between client and server int random_hb_delay = rand.nextInt((max_hb_delay - min_hb_delay) + 1) + min_hb_delay; heart_beat_handler.postDelayed(this, random_hb_delay); } }, hb_delay); } } //heart beat checker - check if we have received a heart rate // private void heartBeat(){ // //check if we are still connected. // //if not , reconnect, // //we don't need to actively send heart beats from the client, as it's assumed that we are ALWAYS streaming data. Later, if we have periods of time where no data is sent, we will want to send a heart beat perhaps. but the client doesn't really need to, we just need to check if we are still connected // if (mConnectState == 0) { // restartSocket(); // } // // //or, if haven't been receiving heart beats, restart socket // if (mConnectState == 2) { // if ((System.currentTimeMillis() - lastHeartbeatTime) > (heartbeatInterval * heartbeatPanicX)) { // Log.d(TAG, "DIDN'T RECEIVE HEART BEATS, RESTARTING SOCKET"); // mConnectState = 0; // restartSocket(); // } // } // } // private void heartBeat(){ //check if we are still connected. //if not , reconnect, //if we are connected, send a heart beat to make sure we are still connected if ((mConnectState == 0) && (shouldDie == false)) { restartSocket(); } else if (mConnectState == 2){ //make sure we don't have a ton of outbound heart beats unresponded to //reimplement this later -- ASG needs to receive heart beats // if (outbound_heart_beats > 5) { // restartSocket(); // return; // } // // //increment counter // outbound_heart_beats++; // // //send heart beat // sendBytes(heart_beat_id, null); } } // public static void restartSocket() { // Log.d(TAG, "Restarting socket"); // mConnectState = 1; // if (socket != null && (!socket.isClosed())){ // try { // output.close(); // input.close(); // socket.close(); // } catch (IOException e) { // System.out.println("FAILED TO CLOSE SOCKET, SOMETHING IS WRONG"); // } // } // // // //restart socket thread // Log.d(TAG, "starting socket"); // SocketThread = new Thread(new SocketThread()); // SocketThread.start(); // } // private void restartSocket(){ Log.d(TAG, "Running restart socket"); mConnectState = 1; outbound_heart_beats = 0; //close the previous socket now that it's broken/being restarted killSocket(); //make sure socket thread has joined before throwing off a new one try { Log.d(TAG, "Waiting socket thread join"); SocketThread.join(); Log.d(TAG, "Socket thread joined"); } catch (InterruptedException e){ e.printStackTrace(); } //start a new socket thread SocketThread = new Thread(new SocketThread()); SocketThread.start(); } private void killSocket(){ try { if (serverSocket != null && (!serverSocket.isClosed())) { Log.d(TAG, "Closing socket, input, serverSocket, etc."); serverSocket.close(); } if (socket != null){ socket.close(); } if (output != null){ output.close(); } if (input != null){ input.close(); } } catch (IOException e) { e.printStackTrace(); } } // static class SocketThread implements Runnable { // public void run() { // try { // serverSocket = new ServerSocket(PORT); // try { // socket = serverSocket.accept(); // // System.out.println("TRYING TO CONNECT AUDIO STREAM ASG"); // // socket = new Socket(SERVER_IP, SERVER_PORT); // lastHeartbeatTime = System.currentTimeMillis(); // System.out.println("GLBOX CONNECTED!"); // //output = new DataOutputStream(socket.getOutputStream()); // //input = new BufferedReader(new InputStreamReader(socket.getInputStream())); // input = new DataInputStream(new DataInputStream(socket.getInputStream())); // mConnectState = 2; // //make the threads that will send and receive // if (ReceiveThread == null) { //if the thread is null, make a new one (the first one) // ReceiveThread = new Thread(new ReceiveThread()); // ReceiveThread.start(); // } else if (!ReceiveThread.isAlive()) { //if the thread is not null but it's dead, let it join then start a new one // Log.d(TAG, "IN SocketThread, WAITING FOR receive THREAD JOING"); // try { // ReceiveThread.join(); //make sure socket thread has joined before throwing off a new one // } catch (InterruptedException e) { // e.printStackTrace(); // } // Log.d(TAG, "receive JOINED"); // ReceiveThread = new Thread(new ReceiveThread()); // ReceiveThread.start(); // } // } catch(IOException e){ // e.printStackTrace(); // mConnectState = 0; // } // } catch (IOException e) { // Log.d(TAG, "Connection Refused on socket"); // e.printStackTrace(); // mConnectState = 0; // } // } // } // class SocketThread implements Runnable { @Override public void run() { try { Log.d(TAG, "Starting new socket, waiting for connection..."); serverSocket = new ServerSocket(PORT); try { socket = serverSocket.accept(); socket.setSoTimeout(3000); Log.d(TAG, "Got socket connection."); //output = new PrintWriter(socket.getOutputStream(), true); output = new DataOutputStream(socket.getOutputStream()); input = new DataInputStream(new DataInputStream(socket.getInputStream())); mConnectState = 2; if (ReceiveThread == null) { //if the thread is null, make a new one (the first one) ReceiveThread = new Thread(new ReceiveThread()); ReceiveThread.start(); } else if (!ReceiveThread.isAlive()) { //if the thread is not null but it's dead, let it join then start a new one try { ReceiveThread.join(); //make sure socket thread has joined before throwing off a new one } catch (InterruptedException e) { e.printStackTrace(); } ReceiveThread = new Thread(new ReceiveThread()); ReceiveThread.start(); } if (SendThread == null) { //if the thread is null, make a new one (the first one) SendThread = new Thread(new SendThread()); SendThread.start(); } else if (!SendThread.isAlive()) { //if the thread is not null but it's dead, let it join then start a new one try { SendThread.join(); //make sure socket thread has joined before throwing off a new one } catch (InterruptedException e) { e.printStackTrace(); } SendThread = new Thread(new SendThread()); SendThread.start(); } } catch (IOException e) { e.printStackTrace(); mConnectState = 0; } } catch (IOException e) { e.printStackTrace(); mConnectState = 0; } } } public void sendBytes(byte[] id, byte [] data){ //first, send hello byte [] hello = {0x01, 0x02, 0x03}; //then send length of body byte[] len; if (data != null) { len = my_int_to_bb_be(data.length); } else { len = my_int_to_bb_be(0); } //then send id of message type byte [] msg_id = id; //then send data byte [] body = data; //then send end tag - eventually make this unique to the image byte [] goodbye = {0x3, 0x2, 0x1}; //combine those into a payload ByteArrayOutputStream outputStream; try { outputStream = new ByteArrayOutputStream(); outputStream.write(hello); outputStream.write(len); outputStream.write(msg_id); if (body != null) { outputStream.write(body); } outputStream.write(goodbye); } catch (IOException e){ mConnectState = 0; return; } byte [] payload = outputStream.toByteArray(); //send it in a background thread //new Thread(new SendThread(payload)).start(); send_queue.add(payload); } //this sends messages class SendThread implements Runnable { SendThread() { } @Override public void run() { send_queue.clear(); while (true){ if (mConnectState != 2){ break; } if (send_queue.size() > 10){ break; } byte [] data; try { data = send_queue.take(); //block until there is something we can pull out to send } catch (InterruptedException e){ e.printStackTrace(); break; } try { output.write(data); // write the message } catch (java.io.IOException e) { e.printStackTrace(); break; } } throwBrokenSocket(); } } //receives messages private class ReceiveThread implements Runnable { @Override public void run() { //System.out.println("Receive Started, mconnect: " + mConnectState); while (true) { if (mConnectState != 2){ break; } try { int chunk_len = 6416; //until we use a better protocol to specify start and end of packet, we need to to match the number in asg byte [] raw_data = new byte[chunk_len]; input.readFully(raw_data, 0, chunk_len); // read the body EventBus.getDefault().post(new AudioChunkNewEvent(raw_data)); //byte [] plain_audio_bytes = decryptBytes(raw_data); //dataObservable.onNext(plain_audio_bytes); } catch (IOException e) { Log.d(TAG, "Audio service receive thread broken."); e.printStackTrace(); break; } } throwBrokenSocket(); } } public byte[] my_int_to_bb_be(int myInteger){ return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(myInteger).array(); } private void throwBrokenSocket(){ if (mConnectState == 2){ mConnectState = 0; } } public byte [] decryptBytes(byte [] input) { byte [] decryptedBytes = AES.decrypt(input, secretKey); return decryptedBytes; } public void destroy(){ shouldDie = true; dataSubscriber.dispose(); killSocket(); } private void handleDataStream(JSONObject data){ try { String dataType = data.getString(MessageTypes.MESSAGE_TYPE_LOCAL); if (dataType.equals(MessageTypes.AUDIO_CHUNK_ENCRYPTED)) { handleEncryptedData(data); } else if (dataType.equals(MessageTypes.AUDIO_CHUNK_DECRYPTED)){ String encodedPlainData = data.getString(MessageTypes.AUDIO_DATA); byte [] decodedPlainData = Base64.decode(encodedPlainData, Base64.DEFAULT); EventBus.getDefault().post(new AudioChunkNewEvent(decodedPlainData)); } } catch (JSONException e){ e.printStackTrace(); } } //here we decode, decrypt, the encode again. It's not pretty, but it allows us to use a JSON event bus, which makes things way more manageable and modular private void handleEncryptedData(JSONObject data){ try{ String encodedData = data.getString(MessageTypes.AUDIO_DATA); byte [] decodedData = Base64.decode(encodedData, Base64.DEFAULT); byte [] plainData = decryptBytes(decodedData); String encodedPlainData = Base64.encodeToString(plainData, Base64.DEFAULT); //make new object and send as decrypted data // JSONObject decryptedData = new JSONObject(); // decryptedData.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.AUDIO_CHUNK_DECRYPTED); // decryptedData.put(MessageTypes.AUDIO_DATA, encodedPlainData); // dataObservable.onNext(decryptedData); //throw new audio event EventBus.getDefault().post(new AudioChunkNewEvent(plainData)); } catch (JSONException e){ e.printStackTrace(); } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/comms/MessageTypes.java ================================================ package com.teamopensmartglasses.smartglassesmanager.comms; public class MessageTypes { //top level public static final String MESSAGE_TYPE_LOCAL = "MESSAGE_TYPE_LOCAL"; //SERVICE TO ACTIVITY public static final String GLASSES_STATUS_UPDATE = "GLASSES_STATUS_UPDATE"; public static final String CONNECTION_GLASSES_STATUS_UPDATE = "CONNECTION_GLASSES_STATUS_UPDATE"; public static final String CONNECTION_GLASSES_GLASSES_OBJECT = "CONNECTION_GLASSES_GLASSES_OBJECT"; //REFERENCE CARD VIEW public static final String REFERENCE_CARD_SIMPLE_VIEW = "REFERENCE_CARD_SIMPLE_VIEW"; public static final String REFERENCE_CARD_TEXT_WALL_VIEW = "REFERENCE_CARD_TEXT_WALL_VIEW"; public static final String REFERENCE_CARD_SIMPLE_VIEW_TITLE = "REFERENCE_CARD_SIMPLE_VIEW_TITLE"; public static final String REFERENCE_CARD_SIMPLE_VIEW_BODY = "REFERENCE_CARD_SIMPLE_VIEW_BODY"; public static final String REFERENCE_CARD_IMAGE_VIEW = "REFERENCE_CARD_IMAGE_VIEW"; public static final String REFERENCE_CARD_IMAGE_VIEW_TITLE = "REFERENCE_CARD_IMAGE_VIEW_TITLE"; public static final String REFERENCE_CARD_IMAGE_VIEW_BODY = "REFERENCE_CARD_IMAGE_VIEW_BODY"; public static final String REFERENCE_CARD_TEXT_WALL_TEXT = "REFERENCE_CARD_TEXT_WALL_TEXT"; public static final String REFERENCE_CARD_IMAGE_VIEW_IMG_URL = "REFERENCE_CARD_IMAGE_VIEW_IMG_URL"; //SCROLLING TEXT VIEW public static final String SCROLLING_TEXT_VIEW_START = "SCROLLING_TEXT_VIEW_START"; public static final String SCROLLING_TEXT_VIEW_FINAL = "SCROLLING_TEXT_VIEW_FINAL"; public static final String SCROLLING_TEXT_VIEW_INTERMEDIATE = "SCROLLING_TEXT_VIEW_INTERMEDIATE"; public static final String SCROLLING_TEXT_VIEW_TEXT = "SCROLLING_TEXT_VIEW_TEXT"; public static final String SCROLLING_TEXT_VIEW_TITLE = "SCROLLING_TEXT_VIEW_TITLE"; public static final String SCROLLING_TEXT_VIEW_STOP = "SCROLLING_TEXT_VIEW_STOP"; //GLASSES CONNECTION EVENT/STATE public static final int CONNECTED_GLASSES = 2; public static final int DISCONNECTED_GLASSES = 0; public static final int TRYNA_CONNECT_GLASSES = 1; public static final int FAILED_CONNECTING_GLASSES = 3; //DATA TYPES public static final String POV_IMAGE = "POV_IMAGE"; public static final String JPG_BYTES_BASE64 = "JPG_BYTES_BASE64"; public static final String IMAGE_ID = "IMAGE_ID"; //specific message types (after MESSAGE_TYPE_*) //TRANSCRIPTS public static final String FINAL_TRANSCRIPT = "FINAL_TRANSCRIPT"; public static final String INTERMEDIATE_TRANSCRIPT = "INTERMEDIATE_TRANSCRIPT"; public static final String FINAL_TRANSCRIPT_FOREIGN = "FINAL_TRANSCRIPT_FOREIGN"; public static final String INTERMEDIATE_TRANSCRIPT_FOREIGN = "INTERMEDIATE_TRANSCRIPT_FOREIGN"; public static final String TRANSCRIPT_TEXT = "TRANSCRIPT_TEXT"; public static final String TRANSCRIPT_ID = "TRANSCRIPT_ID"; public static final String TIMESTAMP = "TIMESTAMP"; //VOICE COMMANDS public static final String VOICE_COMMAND_RESPONSE = "VOICE_COMMAND_RESPONSE"; public static final String COMMAND_RESULT = "COMMAND_RESULT"; public static final String COMMAND_NAME = "COMMAND_NAME"; public static final String COMMAND_RESPONSE_DISPLAY_STRING = "COMMAND_RESPONSE_DISPLAY_STRING"; //voice command event public static final String VOICE_COMMAND_STREAM_EVENT = "VOICE_COMMAND_STREAM_EVENT"; public static final String VOICE_COMMAND_STREAM_EVENT_TYPE = "VOICE_COMMAND_STREAM_EVENT_TYPE"; public static final String WAKE_WORD_EVENT_TYPE = "WAKE_WORD_EVENT_TYPE"; public static final String COMMAND_EVENT_TYPE = "COMMAND_EVENT_TYPE"; public static final String CANCEL_EVENT_TYPE = "CANCEL_EVENT_TYPE"; public static final String RESOLVE_EVENT_TYPE = "RESOLVE_EVENT_TYPE"; public static final String TEXT_RESPONSE_EVENT_TYPE = "TEXT_RESPONSE_EVENT_TYPE"; public static final String COMMAND_ARGS_EVENT_TYPE = "COMMAND_ARGS_EVENT_TYPE"; public static final String REQUIRED_ARG_EVENT_TYPE = "REQUIRED_ARG_EVENT_TYPE"; public static final String ARG_NAME = "ARG_NAME"; public static final String ARG_OPTIONS = "ARG_OPTIONS"; public static final String INPUT_VOICE_STRING = "INPUT_VOICE_STRING"; public static final String VOICE_ARG_EXPECT_TYPE = "VOICE_ARG_EXPECT_TYPE"; public static final String VOICE_ARG_EXPECT_NATURAL_LANGUAGE = "VOICE_ARG_EXPECT_NATURAL_LANGUAGE"; public static final String VOICE_COMMAND_LIST = "VOICE_COMMAND_LIST"; public static final String INPUT_WAKE_WORD = "INPUT_WAKE_WORD"; public static final String INPUT_VOICE_COMMAND_NAME = "INPUT_VOICE_COMMAND_NAME"; //FACE/PERSON SIGHTING public static final String FACE_SIGHTING_EVENT = "FACE_SIGHTING_EVENT"; public static final String FACE_NAME = "FACE_NAME"; //SMS public static final String SMS_REQUEST_SEND = "SMS_REQUEST_SEND"; public static final String SMS_MESSAGE_TEXT = "SMS_MESSAGE_TEXT"; public static final String SMS_PHONE_NUMBER = "SMS_PHONE_NUMBER"; //AUDIO public static final String AUDIO_CHUNK_ENCRYPTED = "AUDIO_CHUNK_ENCRYPTED"; public static final String AUDIO_CHUNK_DECRYPTED = "AUDIO_CHUNK_DECRYPTED"; public static final String AUDIO_DATA = "AUDIO_DATA"; //AUTOCITER/WEARABLE-REFERENCER public static final String AUTOCITER_START = "AUTOCITER_START"; public static final String AUTOCITER_STOP = "AUTOCITER_STOP"; public static final String AUTOCITER_PHONE_NUMBER = "AUTOCITER_PHONE_NUMBER"; public static final String AUTOCITER_POTENTIAL_REFERENCES = "AUTOCITER_POTENTIAL_REFERENCES"; public static final String AUTOCITER_REFERENCE_DATA = "AUTOCITER_REFERENCE_DATA"; //request user UI to display a list of possible choices to dipslay public static final String REFERENCE_SELECT_REQUEST = "REFERENCE_SELECT_REQUEST"; public static final String REFERENCES = "REFERENCES"; //command responses to show //Natural language public final static String NATURAL_LANGUAGE_QUERY = "NATURAL_LANGUAGE_QUERY"; public final static String TEXT_QUERY = "TEXT_QUERY"; //visual search public final static String VISUAL_SEARCH_RESULT = "VISUAL_SEARCH_RESULT"; //this is the ASG facing term public final static String VISUAL_SEARCH_IMAGE= "VISUAL_SEARCH_IMAGE"; public final static String VISUAL_SEARCH_QUERY = "VISUAL_SEARCH_QUERY"; //this is the glbox facing term public final static String VISUAL_SEARCH_DATA = "VISUAL_SEARCH_DATA"; //this is the payload //search engine public final static String SEARCH_ENGINE_QUERY = "SEARCH_ENGINE_QUERY"; public final static String SEARCH_ENGINE_RESULT = "SEARCH_ENGINE_RESULT"; public final static String SEARCH_ENGINE_RESULT_DATA = "SEARCH_ENGINE_RESULT_DATA"; public final static String SEARCH_ENGINE_RESULT_TITLE = "SEARCH_ENGINE_RESULT_TITLE"; public final static String SEARCH_ENGINE_RESULT_BODY = "SEARCH_ENGINE_RESULT_BODY"; public final static String SEARCH_ENGINE_RESULT_IMAGE = "SEARCH_ENGINE_RESULT_IMAGE"; //translation public final static String TRANSLATE_TEXT_QUERY = "TRANSLATE_TEXT_QUERY"; public final static String TRANSLATE_TEXT_DATA = "TRANSLATE_TEXT_DATA"; public final static String TRANSLATE_TEXT_RESULT = "TRANSLATE_TEXT_RESULT"; public final static String TRANSLATE_TEXT_RESULT_DATA = "TRANSLATION_RESULT_DATA"; public final static String START_FOREIGN_LANGUAGE_ASR = "START_FOREIGN_LANGUAGE_ASR"; public final static String STOP_FOREIGN_LANGUAGE_ASR = "STOP_FOREIGN_LANGUAGE_ASR"; public final static String START_FOREIGN_LANGUAGE_SOURCE_LANGUAGE_NAME = "START_FOREIGN_LANGUAGE_SOURCE_LANGUAGE_NAME"; //contextual/semantic search public final static String START_CONTEXTUAL_SEARCH = "START_CONTEXTUAL_SEARCH"; public final static String STOP_CONTEXTUAL_SEARCH = "STOP_CONTEXTUAL_SEARCH"; public final static String CONTEXTUAL_SEARCH_REQUEST = "CONTEXTUAL_SEARCH_REQUEST"; public final static String CONTEXTUAL_SEARCH_RESULT = "CONTEXTUAL_SEARCH_RESULT"; public final static String CONTEXTUAL_SEARCH_RESULT_DATA = "CONTEXTUAL_SEARCH_RESULT_DATA"; //object translation public final static String START_OBJECT_DETECTION = "START_OBJECT_DETECTION"; public final static String STOP_OBJECT_DETECTION = "STOP_OBJECT_DETECTION"; public final static String OBJECT_TRANSLATION_REQUEST = "OBJECT_TRANSLATION_REQUEST"; public final static String OBJECT_TRANSLATION_RESULT = "OBJECT_TRANSLATION_RESULT"; public final static String OBJECT_TRANSLATION_RESULT_DATA = "OBJECT_TRANSLATION_RESULT_DATA"; public final static String AFFECTIVE_SUMMARY_RESULT = "AFFECTIVE_SUMMARY_RESULT"; public final static String COMMAND_SWITCH_MODE = "COMMAND_SWITCH_MODE"; //select command public final static String ACTION_SELECT_COMMAND = "ACTION_SELECT_COMMAND"; public final static String SELECTION = "SELECTION"; //translate public static final String REFERENCE_TRANSLATE_SEARCH_QUERY = "REFERENCE_TRANSLATE_SEARCH_QUERY"; public static final String REFERENCE_TRANSLATE_DATA = "REFERENCE_TRANSLATE_DATA"; public static final String REFERENCE_TRANSLATE_TARGET_LANGUAGE_CODE = "REFERENCE_TRANSLATE_TARGET_LANGUAGE_CODE"; public static final String REFERENCE_TRANSLATE_RESULT = "REFERENCE_TRANSLATE_RESULT"; public static final String REFERENCE_TRANSLATE_RESULT_DATA = "REFERENCE_TRANSLATE_RESULT_DATA"; //text to speech public static final String TEXT_TO_SPEECH_SPEAK = "TEXT_TO_SPEECH_SPEAK"; public static final String TEXT_TO_SPEECH_SPEAK_DATA = "TEXT_TO_SPEECH_SPEAK_DATA"; public static final String TEXT_TO_SPEECH_TARGET_LANGUAGE_CODE = "TEXT_TO_SPEECH_TARGET_LANGUAGE_CODE"; //control the current mode of the ASG public final static String ACTION_SWITCH_MODES = "ACTION_SWITCH_MODES"; public final static String NEW_MODE = "NEW_MODE"; public final static String MODE_HOME = "MODE_HOME"; public final static String MODE_VISUAL_SEARCH = "MODE_VISUAL_SEARCH"; public final static String MODE_LIVE_LIFE_CAPTIONS = "MODE_LIVE_LIFE_CAPTIONS"; public final static String MODE_SOCIAL_MODE = "MODE_SOCIAL_MODE"; public final static String MODE_CONVERSATION_MODE = "MODE_CONVERSATION_MODE"; public final static String MODE_CONTEXTUAL_SEARCH = "MODE_CONTEXTUAL_SEARCH"; public final static String MODE_REFERENCE_GRID = "MODE_REFERENCE_GRID"; public final static String MODE_WEARABLE_FACE_RECOGNIZER = "MODE_WEARABLE_FACE_RECOGNIZER"; public final static String MODE_LANGUAGE_TRANSLATE = "MODE_LANGUAGE_TRANSLATE"; public final static String MODE_OBJECT_TRANSLATE = "MODE_OBJECT_TRANSLATE"; public final static String MODE_TEXT_LIST = "MODE_TEXT_LIST"; public final static String MODE_TEXT_BLOCK = "MODE_TEXT_BLOCK"; public final static String MODE_BLANK = "MODE_BLANK"; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/comms/VolleyCallback.java ================================================ package com.teamopensmartglasses.smartglassesmanager.comms; import org.json.JSONObject; public interface VolleyCallback { void onSuccess(JSONObject result); void onFailure(); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/AudioChunkNewEvent.java ================================================ package com.teamopensmartglasses.augmentoslib.events; public class AudioChunkNewEvent { public byte [] thisChunk; public AudioChunkNewEvent(byte [] thisChunk){ this.thisChunk = thisChunk; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/DisableBleScoAudioEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class DisableBleScoAudioEvent { public boolean disableBleScoAudio; public DisableBleScoAudioEvent(boolean disableBleScoAudio){ this.disableBleScoAudio = disableBleScoAudio; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/GoogleAudioChunkNewEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class GoogleAudioChunkNewEvent { public byte [] thisChunk; public GoogleAudioChunkNewEvent(byte [] thisChunk){ this.thisChunk = thisChunk; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/PauseAsrEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class PauseAsrEvent { public boolean pauseAsr; public PauseAsrEvent(boolean pauseAsr){ this.pauseAsr = pauseAsr; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/PostGenericGlobalMessageEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class PostGenericGlobalMessageEvent { public String message; public PostGenericGlobalMessageEvent(String newMessage){ this.message = newMessage; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/ScoStartEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class ScoStartEvent { public boolean scoStart; public ScoStartEvent(boolean scoStart){ this.scoStart = scoStart; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/SetFontSizeEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.SmartGlassesFontSize; public class SetFontSizeEvent { public SmartGlassesFontSize fontSize; public SetFontSizeEvent(SmartGlassesFontSize newFontSize){ this.fontSize = newFontSize; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/SmartGlassesConnectedEvent.java ================================================ package com.teamopensmartglasses.augmentoslib.events; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.SmartGlassesDevice; public class SmartGlassesConnectedEvent { public final SmartGlassesDevice device; public SmartGlassesConnectedEvent(SmartGlassesDevice newDevice) { this.device = newDevice; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/SmartGlassesConnectionEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class SmartGlassesConnectionEvent { public final int connectionStatus; public SmartGlassesConnectionEvent(int connectionStatus) { this.connectionStatus = connectionStatus; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/TextToSpeechEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class TextToSpeechEvent { public String text; public String language; //the name of the language, fully spelled out, ll lowercase public TextToSpeechEvent(String text, String language){ this.text = text; this.language = language; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/eventbusmessages/VoskAudioChunkNewEvent.java ================================================ package com.teamopensmartglasses.smartglassesmanager.eventbusmessages; public class VoskAudioChunkNewEvent { public byte [] thisChunk; public VoskAudioChunkNewEvent(byte [] thisChunk){ this.thisChunk = thisChunk; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/hci/AudioChunkCallback.java ================================================ package com.teamopensmartglasses.smartglassesmanager.hci; import java.nio.ByteBuffer; public interface AudioChunkCallback{ void onSuccess(ByteBuffer chunk); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/hci/HearItBleMicrophone.java ================================================ package com.teamopensmartglasses.smartglassesmanager.hci; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; public class HearItBleMicrophone { private static final String TAG = "HearItBleMicrophone"; private static final UUID VOICE_SERVICE_UUID = UUID.fromString("00001853-0000-1000-8000-00805f9b34fb"); private static final UUID VOICE_DATA_CHARACTERISTIC_UUID = UUID.fromString("00002bcd-0000-1000-8000-00805f9b34fb"); private BluetoothAdapter bluetoothAdapter; private BluetoothGatt bluetoothGatt; private BluetoothGattCharacteristic voiceDataCharacteristic; private Context context; private AtomicBoolean isConnected = new AtomicBoolean(false); private boolean foundHearIt = false; private final int[] idxtbl = {-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8}; private final int[] steptbl = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; private int lastPacketNumber = -1; public interface HearItBleMicCallback { void onConnected(); void onPcmDataAvailable(byte[] pcmData); } private HearItBleMicCallback hearItBleMicCallback; public void setHearItBleMicCallback(HearItBleMicCallback hearItBleMicCallback) { this.hearItBleMicCallback = hearItBleMicCallback; } public boolean isConnected(){ return isConnected.get(); } public HearItBleMicrophone(Context context) { this.context = context; this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } public void startScanning() { if (!bluetoothAdapter.isEnabled()) { Log.e(TAG, "Bluetooth is not enabled. Cannot start scanning."); return; } bluetoothAdapter.startLeScan(leScanCallback); } private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { if (!foundHearIt && device.getName() != null && device.getName().startsWith("GMIC")) { foundHearIt = true; bluetoothAdapter.stopLeScan(leScanCallback); connectToDevice(device); } } }; private void connectToDevice(BluetoothDevice device) { if (!bluetoothAdapter.isEnabled()) { Log.e(TAG, "Bluetooth is not enabled. Cannot connect to device."); return; } bluetoothGatt = device.connectGatt(context, false, gattCallback); } private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { Log.i(TAG, "Connected to GATT server."); bluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.i(TAG, "Disconnected from GATT server."); isConnected.set(false); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { List services = bluetoothGatt.getServices(); for (BluetoothGattService service : services) { if (VOICE_SERVICE_UUID.equals(service.getUuid())) { voiceDataCharacteristic = service.getCharacteristic(VOICE_DATA_CHARACTERISTIC_UUID); if (voiceDataCharacteristic != null) { bluetoothGatt.setCharacteristicNotification(voiceDataCharacteristic, true); isConnected.set(true); if (hearItBleMicCallback != null) { hearItBleMicCallback.onConnected(); } } } } } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (VOICE_DATA_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) { byte[] adpcmData = characteristic.getValue(); int sampleCount = adpcmData[3] * 2; ByteBuffer pcmDataBuffer = ByteBuffer.allocate(sampleCount * 2).order(ByteOrder.LITTLE_ENDIAN); // Dynamically allocate buffer adpcmToPcm(adpcmData, pcmDataBuffer); byte[] pcmData = pcmDataBuffer.array(); if (hearItBleMicCallback != null) { hearItBleMicCallback.onPcmDataAvailable(pcmData); } } } }; private void adpcmToPcm(byte[] adpcmData, ByteBuffer pcmDataBuffer) { int sampleCount = adpcmData[3] * 2; int currentPacketNumber = -1; // Check and set currentPacketNumber if (adpcmData.length == sampleCount / 2 + 6) { currentPacketNumber = (adpcmData[adpcmData.length - 1] << 8) + adpcmData[adpcmData.length - 2]; if (lastPacketNumber != -1 && currentPacketNumber != lastPacketNumber + 1) { int lostPackets = currentPacketNumber - lastPacketNumber - 1; Log.w(TAG, "Warning: Packet loss detected, number of lost packets: " + lostPackets); } lastPacketNumber = currentPacketNumber; } int predict = (adpcmData[0] | (adpcmData[1] << 8)) << 16 >> 16; int predict_idx = adpcmData[2]; byte[] pcode = new byte[adpcmData.length - 4]; System.arraycopy(adpcmData, 4, pcode, 0, pcode.length); int code = pcode[0]; for (int i = 0; i < sampleCount; i++) { int step = steptbl[predict_idx]; int diffq = step >> 3; if ((code & 4) != 0) diffq += step; step >>= 1; if ((code & 2) != 0) diffq += step; step >>= 1; if ((code & 1) != 0) diffq += step; if ((code & 8) != 0) predict -= diffq; else predict += diffq; predict = Math.max(-32768, Math.min(32767, predict)); predict_idx = Math.max(0, Math.min(88, predict_idx + idxtbl[code & 15])); if (i % 2 == 0) code = pcode[i / 2]; else code >>= 4; pcmDataBuffer.putShort((short) predict); } } public void destroy() { if (bluetoothGatt != null) { bluetoothGatt.disconnect(); bluetoothGatt.close(); bluetoothGatt = null; } hearItBleMicCallback = null; isConnected.set(false); Log.i(TAG, "Bluetooth connection closed and callback turned off."); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/hci/MicrophoneLocalAndBluetooth.java ================================================ package com.teamopensmartglasses.smartglassesmanager.hci; //thanks to https://github.com/aahlenst/android-audiorecord-sample/blob/master/src/main/java/com/example/audiorecord/BluetoothRecordActivity.java import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.CountDownTimer; import android.os.Handler; import android.util.Log; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.ScoStartEvent; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.concurrent.atomic.AtomicBoolean; public class MicrophoneLocalAndBluetooth { private static final String TAG = "WearableAi_MicrophoneLocalAndBluetooth"; private static final int SAMPLING_RATE_IN_HZ = 16000; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private final static float BUFFER_SIZE_SECONDS = 0.192f; // gives us 1024*3 = 3072 samples private static final int BUFFER_SIZE_FACTOR = 2; private final int bufferSize; private boolean bluetoothAudio = false; // are we using local audio or bluetooth audio? private boolean shouldUseHearItBleMicrophone = false; // should we use HearIt BLE microphone? private int retries = 0; private int retryLimit = 3; private Handler mHandler; private final AtomicBoolean recordingInProgress = new AtomicBoolean(false); private HearItBleMicrophone hearItBleMicrophone; private final BroadcastReceiver bluetoothStateReceiver = new BroadcastReceiver() { private BluetoothState bluetoothState = BluetoothState.UNAVAILABLE; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) { int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1); switch (state) { case AudioManager.SCO_AUDIO_STATE_CONNECTED: if (mIsStarting) { mIsStarting = false; } if (mIsCountDownOn) { mIsCountDownOn = false; mCountDown.cancel(); } bluetoothAudio = true; startRecording(); break; case AudioManager.SCO_AUDIO_STATE_CONNECTING: handleBluetoothStateChange(BluetoothState.UNAVAILABLE); case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: handleBluetoothStateChange(BluetoothState.UNAVAILABLE); break; case AudioManager.SCO_AUDIO_STATE_ERROR: handleBluetoothStateChange(BluetoothState.UNAVAILABLE); break; } } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { handleNewBluetoothDevice(); } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { handleDisconnectBluetoothDevice(); } } private void handleBluetoothStateChange(BluetoothState state) { if (bluetoothState == state) { return; } bluetoothState = state; bluetoothStateChanged(state); } private void handleNewBluetoothDevice() { retries = 0; mIsCountDownOn = true; mCountDown.start(); } private void handleDisconnectBluetoothDevice() { if (mIsCountDownOn) { mIsCountDownOn = false; mCountDown.cancel(); } bluetoothAudio = false; deactivateBluetoothSco(); startRecording(); } }; private AudioRecord recorder = null; private AudioManager audioManager; private boolean mIsCountDownOn = false; private boolean mIsStarting = false; private Thread recordingThread = null; private Context mContext; private AudioChunkCallback mChunkCallback; public MicrophoneLocalAndBluetooth(Context context, boolean useBluetoothSco, AudioChunkCallback chunkCallback) { this(context, chunkCallback); this.shouldUseHearItBleMicrophone = true; useBluetoothMic(useBluetoothSco); } public MicrophoneLocalAndBluetooth(Context context, AudioChunkCallback chunkCallback) { bufferSize = Math.round(SAMPLING_RATE_IN_HZ * BUFFER_SIZE_SECONDS); mIsStarting = true; mContext = context; audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mChunkCallback = chunkCallback; mHandler = new Handler(); startRecording(); } private void useBluetoothMic(boolean shouldUseBluetoothSco) { bluetoothAudio = shouldUseBluetoothSco; if (shouldUseBluetoothSco) { startBluetoothSco(); } else { stopBluetoothSco(); } if (recordingInProgress.get()) { startRecording(); } } private void startBluetoothSco() { mContext.registerReceiver(bluetoothStateReceiver, new IntentFilter( AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)); mContext.registerReceiver(bluetoothStateReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED)); mContext.registerReceiver(bluetoothStateReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); mIsCountDownOn = true; mCountDown.start(); } private void stopBluetoothSco() { mIsCountDownOn = false; mCountDown.cancel(); try { mContext.unregisterReceiver(bluetoothStateReceiver); } catch (IllegalArgumentException e) { e.printStackTrace(); } } private void startRecording() { if (recorder != null) { stopRecording(); } if (bluetoothAudio) { audioManager.setMode(AudioManager.MODE_IN_CALL); EventBus.getDefault().post(new ScoStartEvent(true)); } else { audioManager.setMode(AudioManager.MODE_NORMAL); EventBus.getDefault().post(new ScoStartEvent(false)); } recorder = new AudioRecord(MediaRecorder.AudioSource.UNPROCESSED, SAMPLING_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize * 2); recorder.startRecording(); recordingInProgress.set(true); recordingThread = new Thread(new RecordingRunnable(), "Recording Thread"); recordingThread.start(); if (bluetoothAudio && shouldUseHearItBleMicrophone) { Log.d(TAG, "Connecting HearItBle... "); hearItBleMicrophone = new HearItBleMicrophone(mContext); hearItBleMicrophone.setHearItBleMicCallback(new HearItBleMicrophone.HearItBleMicCallback() { @Override public void onConnected() { Log.d(TAG, "--- HearItBle connected!"); stopAndroidMics(); } @Override public void onPcmDataAvailable(byte[] pcmData) { ByteBuffer b_buffer = ByteBuffer.allocate(pcmData.length); b_buffer.put(pcmData); mChunkCallback.onSuccess(b_buffer); } }); hearItBleMicrophone.startScanning(); } } private void stopAndroidMics(){ mIsCountDownOn = false; mCountDown.cancel(); deactivateBluetoothSco(); audioManager.setMode(AudioManager.MODE_NORMAL); stopRecording(); } private void stopRecording() { Log.d(TAG, "Running stopRecording..."); if (recorder == null) { Log.d(TAG, "--- Recorder null, exiting."); return; } recordingInProgress.set(false); recorder.stop(); recorder.release(); recorder = null; recordingThread = null; } private void activateBluetoothSco() { retries += 1; if (!audioManager.isBluetoothScoAvailableOffCall()) { Log.e(TAG, "SCO is not available, recording is not possible"); return; } if (audioManager.isBluetoothScoOn()) { audioManager.stopBluetoothSco(); } audioManager.startBluetoothSco(); } private void deactivateBluetoothSco() { audioManager.stopBluetoothSco(); } private void bluetoothStateChanged(BluetoothState state) { if (BluetoothState.UNAVAILABLE == state && recordingInProgress.get()) { bluetoothAudio = false; stopRecording(); deactivateBluetoothSco(); } else if (BluetoothState.AVAILABLE == state && !recordingInProgress.get()) { bluetoothAudio = true; startRecording(); } else if (BluetoothState.AVAILABLE == state && !bluetoothAudio) { bluetoothAudio = true; startRecording(); } } private class RecordingRunnable implements Runnable { @Override public void run() { short[] short_buffer = new short[bufferSize]; ByteBuffer b_buffer = ByteBuffer.allocate(short_buffer.length * 2); while (recordingInProgress.get()) { int result = recorder.read(short_buffer, 0, short_buffer.length); if (result < 0) { Log.d(TAG, "ERROR"); } b_buffer.order(ByteOrder.LITTLE_ENDIAN); b_buffer.asShortBuffer().put(short_buffer); if (hearItBleMicrophone != null && !hearItBleMicrophone.isConnected()) { mChunkCallback.onSuccess(b_buffer); } b_buffer.clear(); } } private String getBufferReadFailureReason(int errorCode) { switch (errorCode) { case AudioRecord.ERROR_INVALID_OPERATION: return "ERROR_INVALID_OPERATION"; case AudioRecord.ERROR_BAD_VALUE: return "ERROR_BAD_VALUE"; case AudioRecord.ERROR_DEAD_OBJECT: return "ERROR_DEAD_OBJECT"; case AudioRecord.ERROR: return "ERROR"; default: return "Unknown (" + errorCode + ")"; } } } enum BluetoothState { AVAILABLE, UNAVAILABLE } private CountDownTimer mCountDown = new CountDownTimer(1201, 400) { @SuppressWarnings("synthetic-access") @Override public void onTick(long millisUntilFinished) { audioManager.startBluetoothSco(); } @SuppressWarnings("synthetic-access") @Override public void onFinish() { mIsCountDownOn = false; bluetoothAudio = false; startRecording(); } }; public void destroy() { stopRecording(); if (hearItBleMicrophone != null) { hearItBleMicrophone.destroy(); } mIsCountDownOn = false; mCountDown.cancel(); deactivateBluetoothSco(); audioManager.setMode(AudioManager.MODE_NORMAL); if (mContext != null) { try { mContext.unregisterReceiver(bluetoothStateReceiver); } catch (IllegalArgumentException e) { e.printStackTrace(); } } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/AndroidSGC.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.os.HandlerThread; import android.os.StrictMode; import android.util.Base64; import android.util.Log; import com.teamopensmartglasses.smartglassesmanager.comms.AspWebsocketServer; import com.teamopensmartglasses.smartglassesmanager.comms.AudioSystem; import com.teamopensmartglasses.smartglassesmanager.comms.MessageTypes; import com.teamopensmartglasses.augmentoslib.events.GlassesPovImageEvent; import com.teamopensmartglasses.smartglassesmanager.utils.NetworkUtils; import org.greenrobot.eventbus.EventBus; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.DatagramSocket; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import io.reactivex.rxjava3.subjects.PublishSubject; public class AndroidSGC extends SmartGlassesCommunicator { private static final String TAG = "WearableAi_AndroidSGC"; PublishSubject dataObservable; private static boolean killme; private static Handler heart_beat_handler; //handler for advertising private Handler adv_handler; //network details public int PORT_NUM = 8891; public DatagramSocket adv_socket; public String adv_key = "WearableAiCyborg"; //SOCKET STUFF //socket public AspWebsocketServer asgWebSocket; public void displayRowsCard(String[] rowStrings){ } //acutal socket ServerSocket serverSocket; Socket socket; boolean shouldDie = false; //socket threads Thread SocketThread = null; Thread ReceiveThread = null; Thread SendThread = null; //queue of data to send through the socket public BlockingQueue queue; //address info public final int SERVER_PORT = 4567; //i/o public DataOutputStream output; public DataInputStream input; public int outbound_heart_beats = 0; //other final byte [] ack_id = {0x13, 0x37}; final byte [] heart_beat_id = {0x19, 0x20}; final byte [] img_id = {0x01, 0x10}; //id for images //audio streaming system AudioSystem audioSystem; Context context; public AndroidSGC(Context context, PublishSubject dataObservable){ super(); this.dataObservable = dataObservable; this.context = context; //create a new queue to hold outbound message queue = new ArrayBlockingQueue(50); killme = false; //state information mConnectState = 0; } //not used/valid yet @Override protected void setFontSizes(){ LARGE_FONT = 3; MEDIUM_FONT = 2; SMALL_FONT = 0; } public void connectToSmartGlasses(){ //open the UDP socket to broadcast our IP address openSocket(); //send broadcast over UDP that tells smart glasses they can find us adv_handler = new Handler(); final int delay = 1000; // 1000 milliseconds == 1 second adv_handler.postDelayed(new Runnable() { public void run() { new Thread(new SendAdvThread()).start(); adv_handler.postDelayed(this, delay); } }, 5); startAsgWebSocketConnection(); audioSystem = new AudioSystem(context, dataObservable); //start first socketThread Log.d(TAG, "running start socket"); startSocket(); } class SendAdvThread extends Thread { public void run() { //send broadcast so smart glasses know our address NetworkUtils.sendBroadcast(adv_key, adv_socket, PORT_NUM, context); } } public void blankScreen(){ // try{ // //build json object to send command result // JSONObject commandResponseObject = new JSONObject(); // commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.ACTION_SWITCH_MODES); // commandResponseObject.put(MessageTypes.NEW_MODE, MessageTypes.MODE_BLANK); // // //send the command result to web socket, to send to asg // dataObservable.onNext(commandResponseObject); // } catch (JSONException e){ // e.printStackTrace(); // } } public void startAsgWebSocketConnection(){ Log.d(TAG, "Starting WebSocket Server"); //String address = "localhost:8887"; //InetSocketAddress inetSockAddress = new InetSocketAddress(address); int port = 8887; asgWebSocket = new AspWebsocketServer(port); asgWebSocket.setObservable(dataObservable); asgWebSocket.start(); Log.d(TAG, "WebSocket Server STARTED"); } //SOCKET STUFF public void startSocket(){ //start first socketThread Log.d(TAG, "socket val in startSocket: " + socket); if (socket == null) { Log.d(TAG, "starting new SocketThread" + socket); connectionEvent(1); SocketThread = new Thread(new SocketThread()); SocketThread.start(); //setup handler to handle keeping connection alive, all subsequent start of SocketThread //start a new handler thread to send heartbeats HandlerThread thread = new HandlerThread("HeartBeater"); thread.start(); heart_beat_handler = new Handler(thread.getLooper()); final int hb_delay = 3000; final int min_hb_delay = 1000; final int max_hb_delay = 2000; Random rand = new Random(); heart_beat_handler.postDelayed(new Runnable() { public void run() { heartBeat(); //random hb_delay for heart beat so as to disallow synchronized failure between client and server int random_hb_delay = rand.nextInt((max_hb_delay - min_hb_delay) + 1) + min_hb_delay; heart_beat_handler.postDelayed(this, random_hb_delay); } }, hb_delay); } else { Log.d(TAG, "socket wasn't null, so not starting"); } } public void openSocket() { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); try { //Open a random port to send the package adv_socket = new DatagramSocket(); adv_socket.setBroadcast(true); } catch (IOException e) { Log.e(TAG, "IOException: " + e.getMessage()); } } class SocketThread implements Runnable { @Override public void run() { Log.d(TAG, "I have started SOCKETTHREAD"); try { if (killme){ Log.d(TAG, "I have killed myself"); return; } Log.d(TAG, "Starting new socket, waiting for connection..."); serverSocket = new ServerSocket(SERVER_PORT); //serverSocket.setSoTimeout(2000); try { socket = serverSocket.accept(); if (killme){ return; } socket.setSoTimeout(5000); Log.d(TAG, "Got socket connection."); //output = new PrintWriter(socket.getOutputStream(), true); output = new DataOutputStream(socket.getOutputStream()); input = new DataInputStream(new DataInputStream(socket.getInputStream())); connectionEvent(2); if (ReceiveThread == null) { //if the thread is null, make a new one (the first one) ReceiveThread = new Thread(new ReceiveThread()); ReceiveThread.start(); } else if (!ReceiveThread.isAlive()) { //if the thread is not null but it's dead, let it join then start a new one try { ReceiveThread.join(); //make sure socket thread has joined before throwing off a new one } catch (InterruptedException e) { e.printStackTrace(); } ReceiveThread = new Thread(new ReceiveThread()); ReceiveThread.start(); } if (SendThread == null) { //if the thread is null, make a new one (the first one) SendThread = new Thread(new SendThread()); SendThread.start(); } else if (!SendThread.isAlive()) { //if the thread is not null but it's dead, let it join then start a new one try { SendThread.join(); //make sure socket thread has joined before throwing off a new one } catch (InterruptedException e) { e.printStackTrace(); } SendThread = new Thread(new SendThread()); SendThread.start(); } } catch (IOException e) { e.printStackTrace(); connectionEvent(0); } } catch (IOException e) { e.printStackTrace(); connectionEvent(0); } } } //receives messages public void heartBeat(){ //check if we are still connected. //if not , reconnect, //if we are connected, send a heart beat to make sure we are still connected if (mConnectState == 0 && !killme) { Log.d(TAG, "heartBeat failing"); restartSocket(); } else if (mConnectState == 2){ //make sure we don't have a ton of outbound heart beats unresponded to if (outbound_heart_beats > 5) { Log.d(TAG, "heartBeat outbounds failing"); restartSocket(); return; } //increment counter outbound_heart_beats++; //send heart beat sendBytes(heart_beat_id, null); } } //receives messages public class ReceiveThread implements Runnable { @Override public void run() { //System.out.println("Receive Started, mconnect: " + mConnectState); while (true) { if (killme){ return; } if (mConnectState != 2){ break; } byte b1, b2; byte [] raw_data = null; byte goodbye1, goodbye2, goodbye3; try { byte hello1 = input.readByte(); // read hello of incoming message byte hello2 = input.readByte(); // read hello of incoming message byte hello3 = input.readByte(); // read hello of incoming message //make sure header is verified if (hello1 != 0x01 || hello2 != 0x02 || hello3 != 0x03){ Log.d(TAG, "Receive thread: broken intro fail"); break; } //length of body int body_len = input.readInt(); //read in message id bytes b1 = input.readByte(); b2 = input.readByte(); //read in message body (if there is one) if (body_len > 0){ raw_data = new byte[body_len]; input.readFully(raw_data, 0, body_len); // read the body } goodbye1 = input.readByte(); // read goodbye of incoming message goodbye2 = input.readByte(); // read goodbye of incoming message goodbye3 = input.readByte(); // read goodbye of incoming message } catch (IOException e) { if (killme){ Log.d(TAG, "Socket closed (by us), cleaning up."); } else { Log.d(TAG, "Socket closed."); e.printStackTrace(); } break; } //make sure footer is verified if (goodbye1 != 0x03 || goodbye2 != 0x02 || goodbye3 != 0x01) { Log.d(TAG, "Receive thread: broken footer fail"); break; } //now process the data that was sent to us if ((b1 == heart_beat_id[0]) && (b2 == heart_beat_id[1])){ //heart beat id tag outbound_heart_beats--; } else if ((b1 == ack_id[0]) && (b2 == ack_id[1])){ //an ack id } else if ((b1 == img_id[0]) && (b2 == img_id[1])){ //an ack id Log.d(TAG, "Got IMAGE"); if (raw_data != null) { //remember the time we received it long imageTime = System.currentTimeMillis(); //ping back the client to let it know we received the message sendBytes(ack_id, null); handleImage(raw_data, imageTime); } } else { Log.d(TAG, "Killing cuz corrupted data"); break; } } Log.d(TAG, "Receive thread throw broken socket"); throwBrokenSocket(); } } public void restartSocket(){ Log.d(TAG, "Restarting socket."); connectionEvent(1); outbound_heart_beats = 0; //close the previous socket now that it's broken/being restarted killSocket(); //make sure socket thread has joined before throwing off a new one try { SocketThread.join(); } catch (InterruptedException e){ e.printStackTrace(); } //start a new socket thread SocketThread = new Thread(new SocketThread()); SocketThread.start(); } public void killSocket(){ try { Log.d(TAG, "Closing socket, input, serverSocket, etc."); if (serverSocket != null && (!serverSocket.isClosed())) { serverSocket.close(); serverSocket = null; } if (socket != null){ socket.close(); socket = null; } if (output != null){ output.close(); output = null; } if (input != null){ input.close(); input = null; } } catch (IOException e) { Log.d(TAG, "killSocket failed"); e.printStackTrace(); } } public byte[] my_int_to_bb_be(int myInteger){ return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(myInteger).array(); } public void sendBytes(byte[] id, byte [] data){ //first, send hello byte [] hello = {0x01, 0x02, 0x03}; //then send length of body byte[] len; if (data != null) { len = my_int_to_bb_be(data.length); } else { len = my_int_to_bb_be(0); } //then send id of message type byte [] msg_id = id; //then send data byte [] body = data; //then send end tag - eventually make this unique to the image byte [] goodbye = {0x3, 0x2, 0x1}; //combine those into a payload ByteArrayOutputStream outputStream; try { outputStream = new ByteArrayOutputStream(); outputStream.write(hello); outputStream.write(len); outputStream.write(msg_id); if (body != null) { outputStream.write(body); } outputStream.write(goodbye); } catch (IOException e){ connectionEvent(0); return; } byte [] payload = outputStream.toByteArray(); //send it in a background thread //new Thread(new SendThread(payload)).start(); queue.add(payload); } //this sends messages class SendThread implements Runnable { SendThread() { } @Override public void run() { queue.clear(); while (true){ if (killme){ return; } if (mConnectState != 2){ break; } if (queue.size() > 10){ break; } byte [] data; try { data = queue.poll(100, TimeUnit.MILLISECONDS); //block until there is something we can pull out to send if (data == null){ continue; } } catch (InterruptedException e){ e.printStackTrace(); break; } try { output.write(data); // write the message } catch (java.io.IOException e) { e.printStackTrace(); break; } } Log.d(TAG, "Send thread throw broken socket"); throwBrokenSocket(); } } public void throwBrokenSocket(){ if (mConnectState == 2){ Log.d(TAG, "Throwing broken socket"); connectionEvent(0); } } //^^^ SOCKET STUFF public int getConnectionState(){ return mConnectState; } public void destroy(){ Log.d(TAG, "Destroying AndroidSGC"); killme = true; //kill AudioSystem audioSystem.destroy(); //kill asgWebSocket asgWebSocket.destroy(); //stop sending heart beats heart_beat_handler.removeCallbacksAndMessages(null); //stop advertising broadcasting IP if (adv_handler != null) { adv_handler.removeCallbacksAndMessages(null); } //stop sockets killSocket(); //kill this socket try { Log.i(TAG, "SOCKETTHREAD TRYNA JOIN"); if (SocketThread != null) { SocketThread.join(); } Log.i(TAG, "SOCKETTHREAD JOINED"); Log.i(TAG, "SENDTTHREAD TRYNA JOIN"); if (SendThread != null) { SendThread.join(); } Log.i(TAG, "SENDTTHREAD JOINED"); Log.i(TAG, "RECEIVE THREAD TRYNA JOIN"); if (ReceiveThread != null) { ReceiveThread.join(); } Log.i(TAG, "RECEIVE THREAD JOINED"); } catch (InterruptedException e){ e.printStackTrace(); Log.d(TAG, "Error waiting for threads to joing"); } } public void displayReferenceCardSimple(String title, String body){ try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.REFERENCE_CARD_SIMPLE_VIEW); commandResponseObject.put(MessageTypes.REFERENCE_CARD_SIMPLE_VIEW_TITLE, title); commandResponseObject.put(MessageTypes.REFERENCE_CARD_SIMPLE_VIEW_BODY, body); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } public void displayTextWall(String text){ Log.d(TAG, "SHOWING TEXT WALL"); try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.REFERENCE_CARD_TEXT_WALL_VIEW); commandResponseObject.put(MessageTypes.REFERENCE_CARD_TEXT_WALL_TEXT, text); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } public void displayDoubleTextWall(String textTop, String textBottom){ Log.d(TAG, "TODO: SHOWING DOUBLE TEXT WALL"); displayTextWall(textTop + textBottom); } public void displayReferenceCardImage(String title, String body, String imgUrl){ try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.REFERENCE_CARD_IMAGE_VIEW); commandResponseObject.put(MessageTypes.REFERENCE_CARD_IMAGE_VIEW_TITLE, title); commandResponseObject.put(MessageTypes.REFERENCE_CARD_IMAGE_VIEW_BODY, body); commandResponseObject.put(MessageTypes.REFERENCE_CARD_IMAGE_VIEW_IMG_URL, imgUrl); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } @Override public void displayBitmap(Bitmap bmp) { } public void displayBulletList(String title, String [] bullets){ displayBulletList(title, bullets, 0); } public void displayBulletList(String title, String [] bullets, int lingerTime){ } public void stopScrollingTextViewMode() { Log.d(TAG, "STOP SCROLLING TEXT VIEW"); try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.SCROLLING_TEXT_VIEW_STOP); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } public void startScrollingTextViewMode(String title){ super.startScrollingTextViewMode(title); Log.d(TAG, "START SCROLLING TEXT VIEW"); try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.SCROLLING_TEXT_VIEW_START); commandResponseObject.put(MessageTypes.SCROLLING_TEXT_VIEW_TITLE, title); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } public void scrollingTextViewIntermediateText(String text){ try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.SCROLLING_TEXT_VIEW_INTERMEDIATE); commandResponseObject.put(MessageTypes.SCROLLING_TEXT_VIEW_TEXT, text); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } public void scrollingTextViewFinalText(String text){ try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.SCROLLING_TEXT_VIEW_FINAL); commandResponseObject.put(MessageTypes.SCROLLING_TEXT_VIEW_TEXT, text); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } public void showHomeScreen(){ try{ //build json object to send command result JSONObject commandResponseObject = new JSONObject(); commandResponseObject.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.ACTION_SWITCH_MODES); commandResponseObject.put(MessageTypes.NEW_MODE, MessageTypes.MODE_HOME); //send the command result to web socket, to send to asg dataObservable.onNext(commandResponseObject); } catch (JSONException e){ e.printStackTrace(); } } public void displayPromptView(String prompt, String [] options){ //generate args list if (options != null) { //required args // try{ // JSONArray argsList = new JSONArray(); // for (String s : options) { // argsList.put(s); // } // JSONObject wakeWordFoundEvent = new JSONObject(); // wakeWordFoundEvent.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.VOICE_COMMAND_STREAM_EVENT); // wakeWordFoundEvent.put(MessageTypes.VOICE_COMMAND_STREAM_EVENT_TYPE, MessageTypes.REQUIRED_ARG_EVENT_TYPE); // wakeWordFoundEvent.put(MessageTypes.ARG_NAME, prompt); // wakeWordFoundEvent.put(MessageTypes.ARG_OPTIONS, argsList); // dataObservable.onNext(wakeWordFoundEvent); // } catch (JSONException e){ // e.printStackTrace(); // } //natural language arg // try { // JSONObject commandFoundEvent = new JSONObject(); // commandFoundEvent.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.VOICE_COMMAND_STREAM_EVENT); // commandFoundEvent.put(MessageTypes.VOICE_COMMAND_STREAM_EVENT_TYPE, MessageTypes.COMMAND_EVENT_TYPE); // commandFoundEvent.put(MessageTypes.INPUT_VOICE_COMMAND_NAME, command); // commandFoundEvent.put(MessageTypes.INPUT_WAKE_WORD, this.wakeWordGiven); // commandFoundEvent.put(MessageTypes.VOICE_ARG_EXPECT_TYPE, MessageTypes.VOICE_ARG_EXPECT_NATURAL_LANGUAGE); // dataObservable.onNext(commandFoundEvent); // } catch (JSONException e){ // e.printStackTrace(); // } //found wake word JSONArray argsList = new JSONArray(); for (String s : options) { argsList.put(s); } try { JSONObject wakeWordFoundEvent = new JSONObject(); wakeWordFoundEvent.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.VOICE_COMMAND_STREAM_EVENT); wakeWordFoundEvent.put(MessageTypes.VOICE_COMMAND_STREAM_EVENT_TYPE, MessageTypes.WAKE_WORD_EVENT_TYPE); wakeWordFoundEvent.put(MessageTypes.VOICE_COMMAND_LIST, argsList.toString()); wakeWordFoundEvent.put(MessageTypes.INPUT_WAKE_WORD, prompt); dataObservable.onNext(wakeWordFoundEvent); } catch (JSONException e){ e.printStackTrace(); } } } public void displayTextLine(String text){ displayReferenceCardSimple("", text); } public void displayCenteredText(String text){ //TODO: Complete this displayTextLine(text); } public void displayCustomContent(String json) { displayReferenceCardSimple("CustomDisplayNotImplemented", json); } public void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageArgs){ try { JSONObject commandFoundEvent = new JSONObject(); commandFoundEvent.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.VOICE_COMMAND_STREAM_EVENT); commandFoundEvent.put(MessageTypes.VOICE_COMMAND_STREAM_EVENT_TYPE, MessageTypes.COMMAND_EVENT_TYPE); commandFoundEvent.put(MessageTypes.INPUT_VOICE_COMMAND_NAME, "myCommand"); commandFoundEvent.put(MessageTypes.INPUT_WAKE_WORD, "myWakeWord"); commandFoundEvent.put(MessageTypes.VOICE_ARG_EXPECT_TYPE, MessageTypes.VOICE_ARG_EXPECT_NATURAL_LANGUAGE); dataObservable.onNext(commandFoundEvent); } catch (JSONException e){ e.printStackTrace(); } } public void updateNaturalLanguageCommandScreen(String naturalLanguageArgs){ try { JSONObject commandFoundEvent = new JSONObject(); commandFoundEvent.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.VOICE_COMMAND_STREAM_EVENT); commandFoundEvent.put(MessageTypes.VOICE_COMMAND_STREAM_EVENT_TYPE, MessageTypes.COMMAND_ARGS_EVENT_TYPE); commandFoundEvent.put(MessageTypes.INPUT_VOICE_STRING, naturalLanguageArgs); dataObservable.onNext(commandFoundEvent); } catch (JSONException e) { e.printStackTrace(); } } public void handleImage(byte [] raw_data, long imageTime){ //convert to bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(raw_data, 0, raw_data.length); //save and process 1 image at set frequency sendPovImage(raw_data, imageTime); } public void sendPovImage(byte [] img, long imageTime){ String encodedImage = Base64.encodeToString(img, Base64.DEFAULT); EventBus.getDefault().post(new GlassesPovImageEvent(encodedImage, imageTime)); } public void setFontSize(SmartGlassesFontSize fontSize){} } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/AudioWearableSGC.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; import android.content.Context; import android.graphics.Bitmap; import android.util.Log; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.TextToSpeechEvent; import org.greenrobot.eventbus.EventBus; public class AudioWearableSGC extends SmartGlassesCommunicator { private static final String TAG = "WearableAi_AndroidWearableSGC"; private static boolean killme; Context context; public AudioWearableSGC(Context context){ super(); //state information killme = false; mConnectState = 0; } public void setFontSizes(){ } public void connectToSmartGlasses(){ connectionEvent(2); } public void blankScreen(){ } public void displayRowsCard(String[] rowStrings){ } public void destroy(){ killme = true; } public void displayReferenceCardSimple(String title, String body){ Log.d(TAG, "TTS reference card"); EventBus.getDefault().post(new TextToSpeechEvent(title + ", " + body, "english")); } public void displayReferenceCardImage(String title, String body, String imgUrl){ Log.d(TAG, "TTS reference card"); EventBus.getDefault().post(new TextToSpeechEvent(title + ", " + body, "english")); } public void displayBulletList(String title, String [] bullets){ displayBulletList(title, bullets, 0); } public void displayBulletList(String title, String [] bullets, int lingerTime){ } public void displayTextWall(String text){} public void displayDoubleTextWall(String textTop, String textBottom){} public void stopScrollingTextViewMode() { } public void startScrollingTextViewMode(String title){ } public void scrollingTextViewIntermediateText(String text){ } public void scrollingTextViewFinalText(String text){ } public void showHomeScreen(){ } public void displayPromptView(String prompt, String [] options){ } public void displayTextLine(String text){ Log.d(TAG, "displayTextLine: " + text); EventBus.getDefault().post(new TextToSpeechEvent(text, "english")); } @Override public void displayBitmap(Bitmap bmp) { } public void displayCenteredText(String text){ //TODO: Find a way to add (optional) pauses between lines? displayTextLine(text); } @Override public void displayCustomContent(String json) { displayTextLine(json); } public void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageArgs){ } public void updateNaturalLanguageCommandScreen(String naturalLanguageArgs){ } public void setFontSize(SmartGlassesFontSize fontSize){} } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/SmartGlassesCommunicator.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; import android.graphics.Bitmap; import com.teamopensmartglasses.augmentoslib.events.GlassesTapOutputEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.SmartGlassesConnectionEvent; import org.greenrobot.eventbus.EventBus; public abstract class SmartGlassesCommunicator { //basic glasses utils/settings public int mConnectState = 0; protected SmartGlassesModes currentMode; public abstract void connectToSmartGlasses(); public abstract void blankScreen(); public abstract void destroy(); public final String commandNaturalLanguageString = "Command: "; public final String finishNaturalLanguageString = "'finish command' when done"; //reference card public abstract void displayReferenceCardSimple(String title, String body); //display text wall public abstract void displayTextWall(String text); public abstract void displayDoubleTextWall(String textTop, String textBottom); public abstract void displayReferenceCardImage(String title, String body, String imgUrl); public abstract void displayBulletList(String title, String [] bullets); public abstract void displayRowsCard(String[] rowStrings); //voice command UI public abstract void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageArgs); public abstract void updateNaturalLanguageCommandScreen(String naturalLanguageArgs); //scrolling text view public void startScrollingTextViewMode(String title){ setMode(SmartGlassesModes.SCROLLING_TEXT_VIEW); } public abstract void scrollingTextViewIntermediateText(String text); public abstract void scrollingTextViewFinalText(String text); public abstract void stopScrollingTextViewMode(); //prompt view card public abstract void displayPromptView(String title, String [] options); //display text line public abstract void displayTextLine(String text); public abstract void displayBitmap(Bitmap bmp); //display centered text public abstract void displayCenteredText(String text); public abstract void displayCustomContent(String json); //home screen public abstract void showHomeScreen(); public abstract void setFontSize(SmartGlassesFontSize fontSize); //fonts public int LARGE_FONT; public int MEDIUM_FONT; public int SMALL_FONT; public SmartGlassesCommunicator(){ setFontSizes(); } //must be run and set font sizes protected abstract void setFontSizes(); public int getConnectionState(){ return mConnectState; } protected boolean isConnected(){ return (mConnectState == 2); } public void connectionEvent(int connectState){ mConnectState = connectState; EventBus.getDefault().post(new SmartGlassesConnectionEvent(mConnectState)); if (isConnected()) { showHomeScreen(); } } public void tapEvent(int num){ EventBus.getDefault().post(new GlassesTapOutputEvent(num, false, System.currentTimeMillis())); } public void setMode(SmartGlassesModes mode){ currentMode = mode; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/SmartGlassesFontSize.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; public enum SmartGlassesFontSize { SMALL, MEDIUM, LARGE } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/SmartGlassesModes.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; public enum SmartGlassesModes { SCROLLING_TEXT_VIEW, SCREEN_OFF } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/TextLineSG.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; public class TextLineSG { private String text; private int fontSize; public TextLineSG(String text, int fontSize){ this.text = text; this.fontSize = fontSize; } public String getText() { return text; } public void setText(String text) { this.text = text; } public int getFontSizeCode() { return fontSize; } public void setFontSize(int fontSize) { this.fontSize = fontSize; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/UltraliteLayoutHelper.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; import com.vuzix.ultralite.Layout; public class UltraliteLayoutHelper { public static String getJsonTag(Layout layout) { return layout.getJsonTag(); } public static String getCustomJsonTag(String layout) { switch (layout) { case "TEXT_TOP_LEFT_ALIGN": return "ttla"; default: return ""; } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/UltraliteSGC.java ================================================ package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Handler; import android.util.Log; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import com.teamopensmartglasses.smartglassesmanager.R; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; import com.vuzix.ultralite.Anchor; import com.vuzix.ultralite.EventListener; import com.vuzix.ultralite.Layout; import com.vuzix.ultralite.TextAlignment; import com.vuzix.ultralite.TextWrapMode; import com.vuzix.ultralite.UltraliteColor; import com.vuzix.ultralite.UltraliteSDK; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; //communicate with ActiveLook smart glasses public class UltraliteSGC extends SmartGlassesCommunicator { private static final String TAG = "WearableAi_UltraliteSGC"; UltraliteSDK ultraliteSdk; UltraliteSDK.Canvas ultraliteCanvas; UltraliteListener ultraliteListener; Layout currentUltraliteLayout; boolean screenToggleOff = false; //should we keep the screen off? LifecycleOwner lifecycleOwner; Context context; public static final int cardLingerTime = 15; private ArrayList rowTextsLiveNow; //ultralite pixel buffer on left side of screen int ultraliteLeftSidePixelBuffer = 40; // Constants for maximum lines and characters per line //depends on size of pixel buffer! //for MEDIUM text! private int maxLines = 12; // Adjusted from 11.5 for practical use private int maxCharsPerLine = 38; // Assuming max 27 characters fit per line on your display //handler to turn off screen Handler goHomeHandler; Runnable goHomeRunnable; //handler to turn off screen/toggle Handler screenOffHandler; Runnable screenOffRunnable; //handler to disconnect Handler killHandler; boolean hasUltraliteControl; boolean screenIsClear; public class UltraliteListener implements EventListener{ @Override public void onTap(int tapCount) { Log.d(TAG, "Ultralite go tap n times: " + tapCount); tapEvent(tapCount); } @Override public void onDisplayTimeout() { Log.d(TAG, "Ultralite display timeout."); } @Override public void onPowerButtonPress(boolean turningOn) { //since we implement our own state for the power turn on/off, we ignore what the ultralite thinks ('turningOn') and use our own state Log.d(TAG, "Ultralites power button pressed: " + turningOn); //flip value of screen toggle screenToggleOff = !turningOn; if (!screenToggleOff) { Log.d(TAG, "screen toggle off NOT on, showing turn ON message"); displayReferenceCardSimple("SGM Connected.", "Screen back on...", 4); } else { Log.d(TAG, "screen toggle off IS on, showing turn OFF message"); // ultraliteCanvas.clear(); // displayReferenceCardSimple("Toggling off....", "Toggling off...", -1); // screenOffHandler.removeCallbacksAndMessages(this); // screenOffHandler.removeCallbacksAndMessages(screenOffRunnable); // screenOffRunnable = new Runnable() { // @Override // public void run() { // ultraliteSdk.screenOff(); // } // }; // screenOffHandler.postDelayed(screenOffRunnable, 2200); } } } public UltraliteSGC(Context context, LifecycleOwner lifecycleOwner) { super(); this.lifecycleOwner = lifecycleOwner; this.context = context; mConnectState = 0; hasUltraliteControl = false; screenIsClear = true; goHomeHandler = new Handler(); screenOffHandler = new Handler(); killHandler = new Handler(); rowTextsLiveNow = new ArrayList(); ultraliteSdk = UltraliteSDK.get(context); ultraliteListener = new UltraliteListener(); ultraliteSdk.addEventListener(ultraliteListener); LiveData ultraliteConnectedLive = ultraliteSdk.getConnected(); ultraliteConnectedLive.observe(lifecycleOwner, isConnected -> { onUltraliteConnectedChange(isConnected); }); LiveData ultraliteControlled = ultraliteSdk.getControlledByMe(); ultraliteControlled.observe(lifecycleOwner, isControlled -> { onUltraliteControlChanged(isControlled); }); // if (ultraliteSdk.isAvailable()){ // Log.d(TAG, "Ultralite SDK is available."); // } else { // Log.d(TAG, "Ultralite SDK is NOT available."); // } } private void onUltraliteConnectedChange(boolean isConnected) { Log.d(TAG, "Ultralite CONNECT changed to: " + isConnected); if (isConnected) { Log.d(TAG, "Ultralite requesting control..."); boolean isControlled = ultraliteSdk.requestControl(); if (isControlled){ // setupUltraliteCanvas(); changeUltraliteLayout(Layout.CANVAS); } else { return; } Log.d(TAG, "Ultralite RESULT control request: " + isControlled); connectionEvent(2); } else { Log.d(TAG, "Ultralite not connected."); connectionEvent(0); } } private void onUltraliteControlChanged(boolean isControlledByMe) { Log.d(TAG, "Ultralite CONTROL changed to: " + isControlledByMe); if(isControlledByMe) { hasUltraliteControl = true; setupUltraliteCanvas(); connectionEvent(2); displayReferenceCardSimple("Connected to SGM", "by TeamOpenSmartGlasses", 5); } else { hasUltraliteControl = false; } // mUltraliteControlledByMe = isControlledByMe; } @Override protected void setFontSizes(){ } @Override public void connectToSmartGlasses(){ Log.d(TAG, "connectToSmartGlasses running..."); // int mCount = 10; // while ((mConnectState != 2) && (!hasUltraliteControl) && (mCount > 0)){ // mCount--; // try { // Log.d(TAG, "Don't have Ultralite yet, let's wait for it..."); // Thread.sleep(200); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } // Log.d(TAG, "Connected to Ultralites."); // Log.d(TAG, "mCOnnectestate: " + mConnectState); // Log.d(TAG, "mCOunt: " + mCount); // displayReferenceCardSimple("Connected to SGM.", "Authors: TeamOpenSmartGlasses"); // connectionEvent(mConnectState); Log.d(TAG, "connectToSmartGlasses finished"); } public void displayTextLine(String text){ displayReferenceCardSimple("", text); } private static final int MAX_LINES = 7; public void displayTextWall(String text) { String cleanedText = cleanText(text); if (screenToggleOff) { return; } goHomeHandler.removeCallbacksAndMessages(null); goHomeHandler.removeCallbacksAndMessages(goHomeRunnable); Log.d(TAG, "Ultralite is doing text wall"); // Cut text wall down to the largest number of lines possible to display String[] lines = cleanedText.split("\n"); StringBuilder truncatedText = new StringBuilder(); for (int i = 0; i < Math.min(lines.length, MAX_LINES); i++) { truncatedText.append(lines[i]).append("\n"); } // changeUltraliteLayout(Layout.TEXT_BOTTOM_LEFT_ALIGN); changeUltraliteLayout(Layout.TEXT_BOTTOM_LEFT_ALIGN); ultraliteSdk.sendText(truncatedText.toString().trim()); // changeUltraliteLayout(Layout.CANVAS); // ultraliteCanvas.removeText(0); //remove last text we added // Anchor ultraliteAnchor = Anchor.TOP_LEFT; // TextAlignment ultraliteAlignment = TextAlignment.LEFT; // int textId = ultraliteCanvas.createText(text, ultraliteAlignment, UltraliteColor.WHITE, ultraliteAnchor, ultraliteLeftSidePixelBuffer, 0, 640 - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); //// ultraliteCanvas.createText(title, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_LEFT, ultraliteLeftSidePixelBuffer, 120, 640 - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); // Log.d(TAG, "VUZIX TEXT ID: " + textId); ultraliteCanvas.commit(); screenIsClear = false; } private String cleanText(String input) { // Replace Chinese punctuation with English equivalents String cleaned = input.replace(" ,", ", ") .replace(",", ", ") .replace(" 。", ".") .replace("。", ".") .replace(" !", "!") .replace(" ?", "?") .replace("?", "?") .replace(":", ":") .replace(";", ";") .replace("(", "(") .replace(")", ")") .replace("【", "[") .replace("】", "]") .replace("“", "\"") .replace("”", "\"") .replace("、", ",") // No quotes around this one .replace("‘", "'") .replace("’", "'"); // Fix contractions: handle spaces around apostrophes cleaned = cleaned.replaceAll("\\s+'\\s*", "'"); // Remove any non-breaking spaces and trim leading/trailing spaces // cleaned = cleaned.replace("\u00A0", " ").trim(); return cleaned; } public static int countNewLines(String str) { int count = 0; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == '\n') { count++; } } return count; } public void displayDoubleTextWall(String textTop, String textBottom) { if (screenToggleOff) { return; } textTop = cleanText(textTop); textBottom = cleanText(textBottom); // if (textBottom.endsWith("\n")) { // textBottom = textBottom.substring(0, textBottom.length() - 1); // } goHomeHandler.removeCallbacksAndMessages(null); goHomeHandler.removeCallbacksAndMessages(goHomeRunnable); // int rowsTop = 5; int rowsTop = 5 - countNewLines(textTop); StringBuilder combinedText = new StringBuilder(); combinedText.append(textTop); for (int i = 0; i < rowsTop; i++) { combinedText.append("\n"); } StringBuilder bottomBuilder = new StringBuilder(textBottom); combinedText.append(bottomBuilder); // Display the combined text using TEXT_BOTTOM_LEFT_ALIGN layout changeUltraliteLayout(Layout.TEXT_BOTTOM_LEFT_ALIGN); // ultraliteSdk.sendText(combinedText.toString().trim()); ultraliteSdk.sendText(combinedText.toString()); ultraliteCanvas.commit(); screenIsClear = false; } public void displayCenteredText(String text){ } public void displayCustomContent(String json) { displayReferenceCardSimple("CustomDisplayNotImplemented", json); } public void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageInput){ // int boxDelta = 3; // // if (connectedGlasses != null) { // connectedGlasses.clear(); // showPromptCircle(); // // //show the prompt // lastLocNaturalLanguageArgsTextView = displayText(new TextLineSG(prompt, SMALL_FONT), new Point(0, 11), true); // lastLocNaturalLanguageArgsTextView = new Point(lastLocNaturalLanguageArgsTextView.x, lastLocNaturalLanguageArgsTextView.y + boxDelta); //margin down a tad // // //show the final "finish command" prompt // int finishY = 90; // displayLine(new Point(0, finishY), new Point(100, finishY)); // displayText(new TextLineSG(finishNaturalLanguageString, SMALL_FONT), new Point(0, finishY + 2), true); // // //show the natural language args in a scroll box //// ArrayList nli = new ArrayList<>(); //// nli.add(new TextLineSG(naturalLanguageInput, SMALL_FONT)); //// lastLocNaturalLanguageArgsTextView = scrollTextShow(nli, startScrollBoxY.y + boxDelta, finishY - boxDelta); // } } public void updateNaturalLanguageCommandScreen(String naturalLanguageArgs){ // Log.d(TAG, "Displaynig: " + naturalLanguageArgs); // displayText(new TextLineSG(naturalLanguageArgs, SMALL_FONT), new Point(0, lastLocNaturalLanguageArgsTextView.y)); } public void blankScreen(){ // if (connectedGlasses != null){ // connectedGlasses.clear(); // } } @Override public void destroy(){ if (ultraliteSdk != null){ // displayReferenceCardSimple("Disconnecting...", "Disconnecting Smart Glasses from SGM"); // // //disconnect after slight delay, so our above text gets a chance to show up // killHandler.postDelayed(new Runnable() { // @Override // public void run() { // ultraliteSdk.releaseControl(); // } // }, 800); ultraliteSdk.removeEventListener(ultraliteListener); ultraliteSdk.releaseControl(); } } public void showHomeScreen(){ changeUltraliteLayout(Layout.CANVAS); ultraliteCanvas.clear(); screenIsClear = true; } public void setupUltraliteCanvas(){ Log.d(TAG, "Setting up ultralite canvas"); if (ultraliteSdk != null) { ultraliteCanvas = ultraliteSdk.getCanvas(); } } public void changeUltraliteLayout(Layout chosenLayout) { //don't update layout if it's already setup if (currentUltraliteLayout != null && currentUltraliteLayout == chosenLayout){ return; } currentUltraliteLayout = chosenLayout; ultraliteSdk.setLayout(chosenLayout, 0, true); if (chosenLayout.equals(Layout.CANVAS)){ if (ultraliteCanvas == null){ setupUltraliteCanvas(); } } } public void startScrollingTextViewMode(String title){ super.startScrollingTextViewMode(title); if (ultraliteSdk == null) { return; } //clear the screen ultraliteCanvas.clear(); drawTextOnUltralite(title); } public String addNewlineEveryNWords(String input, int n) { String[] words = input.split("\\s+"); StringBuilder result = new StringBuilder(); for (int i = 0; i < words.length; i++) { result.append(words[i]); if ((i + 1) % n == 0 && i != words.length - 1) { result.append("\n"); } else if (i != words.length - 1) { result.append(" "); } } return result.toString(); } public void drawTextOnUltralite(String text){ //edit the text to add new lines to it because ultralite wrapping doesn't work String wrappedText = addNewlineEveryNWords(text, 6); //display the title at the top of the screen UltraliteColor ultraliteColor = UltraliteColor.WHITE; Anchor ultraliteAnchor = Anchor.TOP_LEFT; TextAlignment ultraliteAlignment = TextAlignment.LEFT; changeUltraliteLayout(Layout.CANVAS); ultraliteCanvas.clear(); ultraliteCanvas.clearBackground(UltraliteColor.DIM); // ultraliteCanvas.createText(text, ultraliteAlignment, ultraliteColor, ultraliteAnchor, true); // ultraliteCanvas.createText(text, ultraliteAlignment, ultraliteColor, Anchor.BOTTOM_LEFT, 0, 0, -1, 80, TextWrapMode.WRAP, true); ultraliteCanvas.createText(wrappedText, ultraliteAlignment, ultraliteColor, ultraliteAnchor, true); //, 0, 0, -1, -1, TextWrapMode.WRAP, true); ultraliteCanvas.commit(); screenIsClear = false; } public Bitmap getBitmapFromDrawable(Resources res) { return BitmapFactory.decodeResource(res, R.drawable.vuzix_shield); } public void displayReferenceCardSimple(String title, String body){ displayReferenceCardSimple(title, body, cardLingerTime); } // public void displayReferenceCardSimple(String title, String body, int lingerTime){ // if (!isConnected()) { // Log.d(TAG, "Not showing reference card because not connected to Ultralites..."); // return; // } // //// String [] bulletPoints = {"first one", "second one", "dogs and cats"}; //// displayBulletList("Cool Bullets:", bulletPoints, 15); // // Log.d(TAG, "Sending text to Ultralite SDK: \n" + body); //// ultraliteSdk.sendText("hello world"); //this is BROKEN in Vuzix ultralite 0.4.2 SDK - crashes Vuzix OEM Platform android app // // //edit the text to add new lines to it because ultralite wrapping doesn't work //// String titleWrapped = addNewlineEveryNWords(title, 6); //// String bodyWrapped = addNewlineEveryNWords(body, 6); // // //display the title at the top of the screen // UltraliteColor ultraliteColor = UltraliteColor.WHITE; // Anchor ultraliteAnchor = Anchor.TOP_LEFT; // TextAlignment ultraliteAlignment = TextAlignment.LEFT; // changeUltraliteLayout(Layout.CANVAS); // ultraliteCanvas.clear(); // ultraliteCanvas.createText(title, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_LEFT, ultraliteLeftSidePixelBuffer, 120, 640 - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.createText(body, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.MIDDLE_LEFT, ultraliteLeftSidePixelBuffer, 0, 640 - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.commit(); // screenIsClear = false; // // homeScreenInNSeconds(lingerTime); // } public void setFontSize(SmartGlassesFontSize fontSize){ int textSize; switch (fontSize){ case SMALL: textSize = 24; maxLines = 14; maxCharsPerLine = 42; break; case MEDIUM: textSize = 29; maxLines = 12; // Adjusted from 11.5 for practical use maxCharsPerLine = 38; // Assuming max 27 characters fit per line on your display break; case LARGE: textSize = 40; maxLines = 7; maxCharsPerLine = 28; break; default: throw new IllegalArgumentException("Unknown font size: " + fontSize); } ultraliteSdk.setFont(null, 0, textSize); } public void displayReferenceCardSimple(String titleStr, String bodyStr, int lingerTime){ if (screenToggleOff){ return; } String title = maybeReverseRTLString(titleStr); String body = maybeReverseRTLString(bodyStr); if (!isConnected()) { Log.d(TAG, "Not showing reference card because not connected to Ultralites..."); return; } changeUltraliteLayout(Layout.CANVAS); ultraliteCanvas.clear(); // String [] bulletPoints = {"first one", "second one", "dogs and cats"}; // displayBulletList("Cool Bullets:", bulletPoints, 15); Log.d(TAG, "Sending text to Ultralite SDK: \n" + body); // ultraliteSdk.sendText("hello world"); //this is BROKEN in Vuzix ultralite 0.4.2 SDK - crashes Vuzix OEM Platform android app //edit the text to add new lines to it because ultralite wrapping doesn't work // String titleWrapped = addNewlineEveryNWords(title, 6); // String bodyWrapped = addNewlineEveryNWords(body, 6); //display title top of scren adn text middle of screen // UltraliteColor ultraliteColor = UltraliteColor.WHITE; // Anchor ultraliteAnchor = Anchor.TOP_LEFT; // TextAlignment ultraliteAlignment = TextAlignment.LEFT; // ultraliteCanvas.createText(title, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_LEFT, ultraliteLeftSidePixelBuffer, 120, 640 - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.createText(body, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.MIDDLE_LEFT, ultraliteLeftSidePixelBuffer, 0, 640 - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); //concat body and title, put text on top right of screen (to not block main view) UltraliteColor ultraliteColor = UltraliteColor.WHITE; Anchor ultraliteAnchor = Anchor.TOP_CENTER; TextAlignment ultraliteAlignment = TextAlignment.LEFT; //ultraliteCanvas.createText(body, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_RIGHT, 0, 0, (640 / 2) - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); if (!title.isEmpty() && !title.equals("")){ ultraliteCanvas.createText(title + ": " + body, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_RIGHT, 0, 0, 640 / 2, -1, TextWrapMode.WRAP, true); } else { ultraliteCanvas.createText(body, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_RIGHT, 0, 0, 640 / 2, -1, TextWrapMode.WRAP, true); } //NOTE: // int createText(@NonNull // String text, // @NonNull // TextAlignment alignment, // @NonNull // UltraliteColor color, // @NonNull // Anchor anchor, // int offsetX, // int offsetY, // int width, // int height, // @Nullable // TextWrapMode wrap, // boolean visible) ultraliteCanvas.commit(); screenIsClear = false; homeScreenInNSeconds(lingerTime); } public void displayBulletList(String title, String [] bullets){ displayBulletList(title, bullets, 14); } public void displayRowsCard(String[] rowStrings){ displayRowsCard(rowStrings, cardLingerTime); } public void displayRowsCard(String[] rowStringList, int lingerTime){ if (screenToggleOff){ return; } String[] rowStrings = maybeReverseRTLStringList(rowStringList); if (!isConnected()) { Log.d(TAG, "Not showing rows card because not connected to Ultralites..."); return; } // changeUltraliteLayout(Layout.CANVAS); // ultraliteCanvas.clear(); //make lines to draw on screen to delineate rows int line_thickness = 3; for (int y = 120; y < 480; y += 120) { ultraliteCanvas.clearBackgroundRect(0, y, 640, line_thickness, UltraliteColor.DIM); } //clear old text for (int i = 0; i < rowTextsLiveNow.size(); i++){ ultraliteCanvas.removeText(i); } //old way to clear old text - vuzix ultralite sdk bug that clear background doesn't clear text? // for (int y = 0; y < 480; y += 120) { // //clear previous text // ultraliteCanvas.clearBackgroundRect(0, y + line_thickness, 640, 120 - line_thickness, UltraliteColor.DIM); // ultraliteCanvas.clearBackgroundRect(0, y + line_thickness, 640, 120 - line_thickness, UltraliteColor.BLACK); // } // ultraliteCanvas.commit(); //display the title at the top of the screen UltraliteColor ultraliteColor = UltraliteColor.WHITE; Anchor ultraliteAnchor = Anchor.TOP_LEFT; TextAlignment ultraliteAlignment = TextAlignment.LEFT; //if no input, just show the lines if (rowStrings.length == 0){ ultraliteCanvas.commit(); screenIsClear = false; // homeScreenInNSeconds(lingerTime); return; } //go throw rows, draw the text, don't do more than 4 int y_start_height = 55; // Reverse rowStrings array Collections.reverse(Arrays.asList(rowStrings)); int numRows = 4; int actualRows = Math.min(rowStrings.length, numRows); for (int i = 0; i < actualRows; i++) { // Calculate the offset to start from the bottom for 1, 2, or 3 values int yOffset = (numRows - actualRows) * 112; int textId = ultraliteCanvas.createText(rowStrings[i], TextAlignment.CENTER, UltraliteColor.WHITE, Anchor.TOP_LEFT, ultraliteLeftSidePixelBuffer, y_start_height + yOffset + (i * 112), 640 - ultraliteLeftSidePixelBuffer, -1, TextWrapMode.WRAP, true); rowTextsLiveNow.add(textId); } ultraliteCanvas.commit(); screenIsClear = false; // homeScreenInNSeconds(cardLingerTime); } public void displayBulletList(String title, String [] bulletList, int lingerTime){ if (screenToggleOff){ return; } String[] bullets = maybeReverseRTLStringList(bulletList); if (!isConnected()) { Log.d(TAG, "Not showing bullet point list because not connected to Ultralites..."); return; } Log.d(TAG, "Sending bullets to Ultralite SDK: " + title); //display the title at the top of the screen UltraliteColor ultraliteColor = UltraliteColor.WHITE; Anchor ultraliteAnchor = Anchor.TOP_LEFT; TextAlignment ultraliteAlignment = TextAlignment.LEFT; changeUltraliteLayout(Layout.CANVAS); ultraliteCanvas.clear(); ultraliteCanvas.createText(title, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_LEFT, 0, 0, 640, -1, TextWrapMode.WRAP, true); int displaceY = 25; int displaceX = 25; for (String bullet : bullets){ ultraliteCanvas.createText("⬤ " + bullet, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_LEFT, displaceX, displaceY, 640 - displaceX, -1, TextWrapMode.WRAP, true); displaceY += 125; } ultraliteCanvas.commit(); screenIsClear = false; // if (lingerTime > 0){ // homeScreenInNSeconds(lingerTime); // } } public void homeScreenInNSeconds(int n){ if (n == -1){ return; } //disconnect after slight delay, so our above text gets a chance to show up goHomeHandler.removeCallbacksAndMessages(null); goHomeHandler.removeCallbacksAndMessages(goHomeRunnable); goHomeRunnable = new Runnable() { @Override public void run() { showHomeScreen(); }}; goHomeHandler.postDelayed(goHomeRunnable, n * 1000); } public void displayBitmap(Bitmap bmp) { Bitmap resizedBmp = Bitmap.createScaledBitmap(bmp, 620, 460, true); // 640 x 480 changeUltraliteLayout(Layout.CANVAS); screenIsClear = false; Log.d(TAG, "Sending bitmap to Ultralite"); ultraliteCanvas.drawBackground(resizedBmp, 50, 80); ultraliteCanvas.commit(); } //don't show images on activelook (screen is too low res) public void displayReferenceCardImage(String title, String body, String imgUrl){ if (screenToggleOff){ return; } changeUltraliteLayout(Layout.CANVAS); ultraliteCanvas.clear(); //make image //below works, but only for very, very low res/size images Anchor ultraliteImageAnchor = Anchor.CENTER; Picasso.get() .load(imgUrl) .into(new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { // Use the bitmap // LVGLImage ultraliteImage = LVGLImage.fromBitmap(getBitmapFromDrawable(context.getResources()), CF_INDEXED_2_BIT); // LVGLImage ultraliteImage = LVGLImage.fromBitmap(bitmap, CF_INDEXED_2_BIT); changeUltraliteLayout(Layout.CANVAS); //send text first, cuz this is fast ultraliteCanvas.createText(title, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.TOP_LEFT, 0, 0, 640, -1, TextWrapMode.WRAP, true); ultraliteCanvas.createText(body, TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.BOTTOM_LEFT, 0, 0, 640, -1, TextWrapMode.WRAP, true); ultraliteCanvas.commit(); screenIsClear = false; Log.d(TAG, "Sending image to Ultralite"); // ultraliteCanvas.createImage(ultraliteImage, ultraliteImageAnchor, 0, 0, true); ultraliteCanvas.drawBackground(bitmap, 0, 0); //sending text again to ultralite in case image overwrote it // ultraliteCanvas.createText(title + "2", TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.BOTTOM_LEFT, 0, 0, 640, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.createText(body + "2", TextAlignment.AUTO, UltraliteColor.WHITE, Anchor.MIDDLE_LEFT, 0, 0, 640, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.commit(); // //display the title at the top of the screen // UltraliteColor ultraliteColor = UltraliteColor.WHITE; // TextAlignment ultraliteAlignment = TextAlignment.LEFT; // // ultraliteCanvas.clearBackground(UltraliteColor.DIM); // ultraliteCanvas.createText(titleWrapped, ultraliteAlignment, ultraliteColor, Anchor.TOP_LEFT, true); //, 0, 0, -1, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.createText(bodyWrapped, ultraliteAlignment, ultraliteColor, Anchor.BOTTOM_LEFT, true); //, 0, 0, -1, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.commit(); // homeScreenInNSeconds(cardLingerTime); } @Override public void onBitmapFailed(Exception e, Drawable errorDrawable) { // Handle the error Log.d(TAG, "Bitmap failed"); e.printStackTrace(); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { // Called before the image is loaded. You can set a placeholder if needed. } }); //edit the text to add new lines to it because ultralite wrapping doesn't work // String titleWrapped = addNewlineEveryNWords(title, 6); // String bodyWrapped = addNewlineEveryNWords(body, 6); // // //display the title at the top of the screen // UltraliteColor ultraliteColor = UltraliteColor.WHITE; // TextAlignment ultraliteAlignment = TextAlignment.LEFT; // //ultraliteCanvas.clearBackground(UltraliteColor.DIM); // ultraliteCanvas.createText(titleWrapped, ultraliteAlignment, ultraliteColor, Anchor.TOP_LEFT, true); //, 0, 0, -1, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.createText(bodyWrapped, ultraliteAlignment, ultraliteColor, Anchor.BOTTOM_LEFT, true); //, 0, 0, -1, -1, TextWrapMode.WRAP, true); // ultraliteCanvas.commit(); // screenIsClear = false; } //handles text wrapping, returns final position of last line printed // private Point displayText(TextLineSG textLine, Point percentLoc, boolean centered){ // if (!isConnected()){ // return null; // } // // //get info about the wrapping // Pair wrapInfo = computeStringWrapInfo(textLine); // int numWraps = (int)wrapInfo.first; // int wrapLenNumChars = (int)wrapInfo.second; // // //loop through the text, writing out individual lines to the glasses // ArrayList chunkedText = new ArrayList<>(); // Point textPoint = percentLoc; // int textMarginY = computeMarginPercent(textLine.getFontSizeCode()); //(fontToSize.get(textLine.getFontSize()) * 1.3) // for (int i = 0; i <= numWraps; i++){ // int startIdx = wrapLenNumChars * i; // int endIdx = Math.min(startIdx + wrapLenNumChars, textLine.getText().length()); // String subText = textLine.getText().substring(startIdx, endIdx).trim(); // chunkedText.add(subText); // TextLineSG thisTextLine = new TextLineSG(subText, textLine.getFontSizeCode()); // if (!centered) { // sendTextToGlasses(thisTextLine, textPoint); // } else { // int xPercentLoc = computeStringCenterInfo(thisTextLine); // sendTextToGlasses(thisTextLine, new Point(xPercentLoc, textPoint.y)); // } // textPoint = new Point(textPoint.x, textPoint.y + pixelToPercent(displayHeightPixels, fontToSize.get(textLine.getFontSizeCode())) + textMarginY); //lower our text for the next loop // } // // return textPoint; // } public void stopScrollingTextViewMode() { // if (connectedGlasses == null) { // return; // } // // //clear the screen // connectedGlasses.clear(); } public void scrollingTextViewIntermediateText(String text){ } public void scrollingTextViewFinalText(String text){ if (!isConnected()){ return; } // //save to our saved list of final scrolling text strings // finalScrollingTextStrings.add(text); // // //get the max number of wraps allows // float allowedTextRows = computeAllowedTextRows(fontToSize.get(scrollingTextTitleFontSize), fontToSize.get(scrollingTextTextFontSize), percentToPixel(displayHeightPixels, computeMarginPercent(scrollingTextTextFontSize))); // // //figure out the maximum we can display // int totalRows = 0; // ArrayList finalTextToDisplay = new ArrayList<>(); // boolean hitBottom = false; // for (int i = finalScrollingTextStrings.toArray().length - 1; i >= 0; i--){ // String finalText = finalScrollingTextStrings.get(i); // //convert to a TextLine type with small font // TextLineSG tlString = new TextLineSG(finalText, SMALL_FONT); // //get info about the wrapping of this string // Pair wrapInfo = computeStringWrapInfo(tlString); // int numWraps = (int)wrapInfo.first; // int wrapLenNumChars = (int)wrapInfo.second; // totalRows += numWraps + 1; // // if (totalRows > allowedTextRows){ // finalScrollingTextStrings = finalTextToDisplay; // lastLocScrollingTextView = belowTitleLocScrollingTextView; // //clear the glasses as we hit our limit and need to redraw // connectedGlasses.color((byte)0x00); // connectedGlasses.rectf(percentScreenToPixelsLocation(belowTitleLocScrollingTextView.x, belowTitleLocScrollingTextView.y), percentScreenToPixelsLocation(100, 100)); // //stop looping, as we've ran out of room // hitBottom = true; // } else { // finalTextToDisplay.add(0, finalText); // } // } // // //display all of the text that we can // if (hitBottom) { //if we ran out of room, we need to redraw all the text // for (String finalString : finalTextToDisplay) { // TextLineSG tlString = new TextLineSG(finalString, scrollingTextTextFontSize); // //write this text at the last location + margin // Log.d(TAG, "Writing string: " + tlString.getText() + finalTextToDisplay.size()); // lastLocScrollingTextView = displayText(tlString, new Point(0, lastLocScrollingTextView.y)); // } // } else { //if we didn't hit the bottom, and there's room, we can just display the next line // TextLineSG tlString = new TextLineSG(text, scrollingTextTextFontSize); // lastLocScrollingTextView = displayText(tlString, new Point(0, lastLocScrollingTextView.y)); // } } public static String maybeReverseRTLString(String text) { StringBuilder result = new StringBuilder(); StringBuilder rtlBuffer = new StringBuilder(); for (char c : text.toCharArray()) { if (isRTLCharacter(c)) { rtlBuffer.append(c); // Append RTL characters to a buffer } else { if (rtlBuffer.length() > 0) { result.append(rtlBuffer.reverse()); // Reverse and append RTL text when a non-RTL character is found rtlBuffer.setLength(0); // Clear the buffer } result.append(c); // Append non-RTL characters directly to the result } } if (rtlBuffer.length() > 0) { result.append(rtlBuffer.reverse()); // Append any remaining RTL text in reverse } return result.toString(); } private static boolean isRTLCharacter(char c) { Character.UnicodeBlock block = Character.UnicodeBlock.of(c); return block == Character.UnicodeBlock.ARABIC || block == Character.UnicodeBlock.HEBREW || block == Character.UnicodeBlock.SYRIAC || block == Character.UnicodeBlock.ARABIC_SUPPLEMENT || block == Character.UnicodeBlock.THAANA || block == Character.UnicodeBlock.NKO || block == Character.UnicodeBlock.SAMARITAN || block == Character.UnicodeBlock.MANDAIC || block == Character.UnicodeBlock.ARABIC_EXTENDED_A; // Add other RTL blocks as needed } public String[] maybeReverseRTLStringList(String[] in){ String[] out = new String[in.length]; for(int i = 0; i < in.length; i++) out[i] = maybeReverseRTLString(in[i]); return out; } public void displayPromptView(String prompt, String [] options){ if (!isConnected()){ return; } // ultraliteCanvas.clear(); // connectedGlasses.clear(); // showPromptCircle(); // // //show the prompt and options, if any // ArrayList promptPageElements = new ArrayList<>(); // promptPageElements.add(new TextLineSG(prompt, LARGE_FONT)); // if (options != null) { // //make an array list of options // for (String s : options){ // promptPageElements.add(new TextLineSG(s, SMALL_FONT)); // } // } // displayLinearStuff(promptPageElements, new Point(0, 11), true); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/ASR_FRAMEWORKS.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition; public enum ASR_FRAMEWORKS { GOOGLE_ASR_FRAMEWORK, DEEPGRAM_ASR_FRAMEWORK, AZURE_ASR_FRAMEWORK } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/SpeechRecFramework.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition; import android.content.Context; public abstract class SpeechRecFramework { private ASR_FRAMEWORKS asrFramework; private Context mContext; public boolean pauseAsrFlag = false; public abstract void start(); public abstract void destroy(); public abstract void ingestAudioChunk(byte [] audioChunk); public void pauseAsr(boolean pauseAsrFlag){ this.pauseAsrFlag = pauseAsrFlag; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/SpeechRecSwitchSystem.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition; import android.content.Context; import android.util.Log; import com.teamopensmartglasses.augmentoslib.events.AudioChunkNewEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.PauseAsrEvent; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.azure.SpeechRecAzure; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.deepgram.SpeechRecDeepgram; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.SpeechRecGoogle; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; //send audio to one of the built in ASR frameworks. public class SpeechRecSwitchSystem { private final String TAG = "WearableAi_SpeechRecSwitchSystem"; private ASR_FRAMEWORKS asrFramework; private SpeechRecFramework speechRecFramework; private SpeechRecGoogle speechRecGoogle; private Context mContext; public String currentLanguage; public SpeechRecSwitchSystem(Context mContext) { this.mContext = mContext; } public void startAsrFramework(ASR_FRAMEWORKS asrFramework) { startAsrFramework(asrFramework, "English"); } public void startAsrFramework(ASR_FRAMEWORKS asrFramework, String language) { //kill old asr EventBus.getDefault().unregister(this); if (speechRecFramework != null){ speechRecFramework.destroy(); } //set language this.currentLanguage = language; //set new asr this.asrFramework = asrFramework; //create new asr if (this.asrFramework == ASR_FRAMEWORKS.GOOGLE_ASR_FRAMEWORK){ speechRecFramework = new SpeechRecGoogle(mContext, language); } else if (this.asrFramework == ASR_FRAMEWORKS.DEEPGRAM_ASR_FRAMEWORK){ speechRecFramework = new SpeechRecDeepgram(mContext, language); } else if (this.asrFramework == ASR_FRAMEWORKS.AZURE_ASR_FRAMEWORK){ speechRecFramework = new SpeechRecAzure(mContext, language); } //start asr speechRecFramework.start(); EventBus.getDefault().register(this); } public void startAsrFramework(ASR_FRAMEWORKS asrFramework, String transcribeLanguage, String sourceLanguage) { //kill old asr EventBus.getDefault().unregister(this); if (speechRecFramework != null){ speechRecFramework.destroy(); } // if (!(this.asrFramework == ASR_FRAMEWORKS.AZURE_ASR_FRAMEWORK)) { // Log.e(TAG, "startAsrFramework: This function is only for Azure ASR"); // return; // } //set language this.currentLanguage = transcribeLanguage; //set new asr this.asrFramework = asrFramework; //create new asr speechRecFramework = new SpeechRecAzure(mContext, transcribeLanguage, sourceLanguage); //start asr speechRecFramework.start(); EventBus.getDefault().register(this); } @Subscribe public void onAudioChunkNewEvent(AudioChunkNewEvent receivedEvent){ //redirect audio to the currently in use ASR framework, if it's not paused if (!speechRecFramework.pauseAsrFlag) { speechRecFramework.ingestAudioChunk(receivedEvent.thisChunk); } } @Subscribe public void onPauseAsrEvent(PauseAsrEvent receivedEvent){ //redirect audio to the currently in use ASR framework speechRecFramework.pauseAsr(receivedEvent.pauseAsr); } public void destroy(){ if (speechRecFramework != null){ speechRecFramework.destroy(); } EventBus.getDefault().unregister(this); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/azure/AzureAudioInputStream.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.azure; import com.microsoft.cognitiveservices.speech.audio.AudioStreamFormat; import com.microsoft.cognitiveservices.speech.audio.PullAudioInputStreamCallback; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class AzureAudioInputStream extends PullAudioInputStreamCallback { private static final int SAMPLE_RATE = 16000; private static final short BITS_PER_SAMPLE = 16; private static final short CHANNELS = 1; private final AudioStreamFormat format; private final BlockingQueue audioQueue; private byte[] leftoverChunk; private int leftoverOffset; private static AzureAudioInputStream instance; private AzureAudioInputStream() { this.format = AudioStreamFormat.getWaveFormatPCM(SAMPLE_RATE, BITS_PER_SAMPLE, CHANNELS); this.audioQueue = new LinkedBlockingQueue<>(); this.leftoverChunk = null; this.leftoverOffset = 0; } public static synchronized AzureAudioInputStream getInstance() { if (instance == null) { instance = new AzureAudioInputStream(); } return instance; } public void push(byte[] audioChunk) { audioQueue.add(audioChunk); } @Override public int read(byte[] dataBuffer) { int bytesRead = 0; try { if (leftoverChunk != null) { int length = Math.min(leftoverChunk.length - leftoverOffset, dataBuffer.length); System.arraycopy(leftoverChunk, leftoverOffset, dataBuffer, 0, length); leftoverOffset += length; bytesRead = length; if (leftoverOffset >= leftoverChunk.length) { leftoverChunk = null; leftoverOffset = 0; } } while (bytesRead < dataBuffer.length) { byte[] chunk = audioQueue.take(); // Blocks if queue is empty int length = Math.min(chunk.length, dataBuffer.length - bytesRead); System.arraycopy(chunk, 0, dataBuffer, bytesRead, length); bytesRead += length; if (length < chunk.length) { leftoverChunk = chunk; leftoverOffset = length; break; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return 0; } return bytesRead; } @Override public void close() { audioQueue.clear(); } public AudioStreamFormat getFormat() { return this.format; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/azure/SpeechRecAzure.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.azure; import android.content.Context; import android.util.Log; import com.microsoft.cognitiveservices.speech.Connection; import com.microsoft.cognitiveservices.speech.PhraseListGrammar; import com.microsoft.cognitiveservices.speech.ProfanityOption; import com.microsoft.cognitiveservices.speech.SpeechConfig; import com.microsoft.cognitiveservices.speech.SpeechRecognizer; import com.microsoft.cognitiveservices.speech.audio.AudioConfig; import com.microsoft.cognitiveservices.speech.translation.SpeechTranslationConfig; import com.microsoft.cognitiveservices.speech.translation.TranslationRecognizer; import com.teamopensmartglasses.augmentoslib.events.SpeechRecOutputEvent; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.SpeechRecFramework; import org.greenrobot.eventbus.EventBus; import java.math.BigInteger; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SpeechRecAzure extends SpeechRecFramework { private static final String API_KEY = "0a2244c410664011bbf33fdb2cdc0f30"; private static final String REGION = "eastasia"; private static final String TAG = "WearableAi_SpeechRecAzure"; private SpeechConfig speechConfig; private SpeechRecognizer speechRecognizer; private SpeechTranslationConfig speechTranslationConfig; private TranslationRecognizer translationRecognizer; private ExecutorService executorService = Executors.newCachedThreadPool(); private Context mContext; private String currentLanguageCode; private String targetLanguageCode; private boolean isTranslation; public SpeechRecAzure(Context context, String languageLocale) { this.mContext = context; this.currentLanguageCode = initLanguageLocale(languageLocale); this.isTranslation = false; } public SpeechRecAzure(Context context, String currentLanguageLocale, String targetLanguageLocale) { this.mContext = context; this.currentLanguageCode = initLanguageLocale(currentLanguageLocale); this.targetLanguageCode = initLanguageLocale(targetLanguageLocale); this.isTranslation = true; } @Override public void start() { Log.d(TAG, "Starting Azure Speech Service"); if (isTranslation) { initializeTranslationRecognizer(); } else { initializeSpeechRecognizer(); } } private void stopReco() { Log.d(TAG, "Attempting to stop continuous recognition."); if (isTranslation && translationRecognizer != null) { executorService.submit(() -> { try { translationRecognizer.stopContinuousRecognitionAsync().get(); Log.i(TAG, "Continuous translation stopped."); } catch (Exception e) { Log.e(TAG, "Failed to stop continuous translation: " + e.getMessage()); } finally { translationRecognizer.close(); translationRecognizer = null; Log.i(TAG, "TranslationRecognizer instance closed and set to null."); AzureAudioInputStream.getInstance().close(); } }); } else if (!isTranslation && speechRecognizer != null) { executorService.submit(() -> { try { speechRecognizer.stopContinuousRecognitionAsync().get(); Log.i(TAG, "Continuous recognition stopped."); } catch (Exception e) { Log.e(TAG, "Failed to stop continuous recognition: " + e.getMessage()); } finally { speechRecognizer.close(); speechRecognizer = null; Log.i(TAG, "SpeechRecognizer instance closed and set to null."); AzureAudioInputStream.getInstance().close(); } }); } } @Override public void destroy() { stopReco(); Log.d(TAG, "--- Azure speech service destroyed."); } @Override public void ingestAudioChunk(byte[] audioChunk) { if (isTranslation && translationRecognizer != null) { AzureAudioInputStream.getInstance().push(audioChunk); } else if (!isTranslation && speechRecognizer != null) { AzureAudioInputStream.getInstance().push(audioChunk); } } private void initializeSpeechRecognizer() { speechConfig = SpeechConfig.fromSubscription(API_KEY, REGION); speechConfig.setSpeechRecognitionLanguage(currentLanguageCode); speechConfig.requestWordLevelTimestamps(); speechConfig.setProfanity(ProfanityOption.Raw); AudioConfig audioConfig = AudioConfig.fromStreamInput(AzureAudioInputStream.getInstance()); speechRecognizer = new SpeechRecognizer(speechConfig, audioConfig); setupPhraseList(speechRecognizer); speechRecognizer.recognizing.addEventListener((o, e) -> { String intermediateResult = e.getResult().getText(); BigInteger offset = e.getResult().getOffset(); if (intermediateResult != null && !intermediateResult.trim().isEmpty()) { EventBus.getDefault().post(new SpeechRecOutputEvent(intermediateResult, offset.longValue(), false)); } }); speechRecognizer.recognized.addEventListener((o, e) -> { String finalResult = e.getResult().getText(); BigInteger offset = e.getResult().getOffset(); if (finalResult != null && !finalResult.trim().isEmpty()) { EventBus.getDefault().post(new SpeechRecOutputEvent(finalResult, offset.longValue(), true)); } }); Connection connection = Connection.fromRecognizer(speechRecognizer); connection.disconnected.addEventListener((s, connectionEventArgs) -> { Log.e(TAG, "Disconnected from Azure Speech Service, sessionId: " + connectionEventArgs.getSessionId()); handleDisconnect(); }); executorService.submit(() -> { try { speechRecognizer.startContinuousRecognitionAsync().get(); Log.i(TAG, "Continuous recognition started."); } catch (Exception e) { Log.e(TAG, "Error starting continuous recognition: " + e.getMessage()); handleDisconnect(); } }); } private void initializeTranslationRecognizer() { speechTranslationConfig = SpeechTranslationConfig.fromSubscription(API_KEY, REGION); speechTranslationConfig.setSpeechRecognitionLanguage(currentLanguageCode); speechTranslationConfig.addTargetLanguage(targetLanguageCode); AudioConfig audioConfig = AudioConfig.fromStreamInput(AzureAudioInputStream.getInstance()); translationRecognizer = new TranslationRecognizer(speechTranslationConfig, audioConfig); translationRecognizer.recognizing.addEventListener((o, e) -> { String intermediateResult = e.getResult().getText(); BigInteger offset = e.getResult().getOffset(); if (intermediateResult != null && !intermediateResult.trim().isEmpty()) { // Get the single entry from the map Map.Entry translation = e.getResult().getTranslations().entrySet().iterator().next(); String translatedText = translation.getValue(); String targetLanguage = translation.getKey(); Log.d(TAG, "Translated into " + targetLanguage + ": " + translatedText); EventBus.getDefault().post(new SpeechRecOutputEvent(intermediateResult, offset.longValue(), false, false)); EventBus.getDefault().post(new SpeechRecOutputEvent(translatedText, offset.longValue(), false, true)); } }); translationRecognizer.recognized.addEventListener((o, e) -> { String finalResult = e.getResult().getText(); BigInteger offset = e.getResult().getOffset(); if (finalResult != null && !finalResult.trim().isEmpty()) { // Get the single entry from the map Map.Entry translation = e.getResult().getTranslations().entrySet().iterator().next(); String translatedText = translation.getValue(); String targetLanguage = translation.getKey(); Log.d(TAG, "Translated into " + targetLanguage + ": " + translatedText); EventBus.getDefault().post(new SpeechRecOutputEvent(finalResult, offset.longValue(), true, false)); EventBus.getDefault().post(new SpeechRecOutputEvent(translatedText, offset.longValue(), true, true)); } }); Connection connection = Connection.fromRecognizer(translationRecognizer); connection.disconnected.addEventListener((s, connectionEventArgs) -> { Log.e(TAG, "Disconnected from Azure Speech Service, sessionId: " + connectionEventArgs.getSessionId()); handleDisconnect(); }); executorService.submit(() -> { try { translationRecognizer.startContinuousRecognitionAsync().get(); Log.i(TAG, "Continuous recognition started."); } catch (Exception e) { Log.e(TAG, "Error starting continuous recognition: " + e.getMessage()); handleDisconnect(); } }); } private void handleDisconnect() { executorService.submit(() -> { boolean connected = false; while (!connected) { try { Thread.sleep(1000); if (speechRecognizer == null){ return; } if (isTranslation) { if (translationRecognizer == null){ return; } translationRecognizer.startContinuousRecognitionAsync().get(); } else { if (speechRecognizer == null){ return; } speechRecognizer.startContinuousRecognitionAsync().get(); } connected = true; Log.i(TAG, "Reconnected and continuous recognition started."); } catch (Exception e) { Log.e(TAG, "Error reconnecting: " + e.getMessage()); } } }); } private void setupPhraseList(SpeechRecognizer speechRecognizer) { PhraseListGrammar phraseListGrammar = PhraseListGrammar.fromRecognizer(speechRecognizer); phraseListGrammar.addPhrase("Hey Mira"); phraseListGrammar.addPhrase("Convoscope"); phraseListGrammar.addPhrase("Team Open Smart Glasses"); phraseListGrammar.addPhrase("smart glasses"); } //below is long list of languages private String initLanguageLocale(String localeString) { switch (localeString) { case "Afrikaans (South Africa)": return "af-ZA"; case "Amharic (Ethiopia)": return "am-ET"; case "Arabic (United Arab Emirates)": return "ar-AE"; case "Arabic (Bahrain)": return "ar-BH"; case "Arabic (Algeria)": return "ar-DZ"; case "Arabic (Egypt)": return "ar-EG"; case "Arabic (Israel)": return "ar-IL"; case "Arabic (Iraq)": return "ar-IQ"; case "Arabic (Jordan)": return "ar-JO"; case "Arabic (Kuwait)": return "ar-KW"; case "Arabic (Lebanon)": return "ar-LB"; case "Arabic (Libya)": return "ar-LY"; case "Arabic (Morocco)": return "ar-MA"; case "Arabic (Oman)": return "ar-OM"; case "Arabic (Palestinian Authority)": return "ar-PS"; case "Arabic (Qatar)": return "ar-QA"; case "Arabic (Saudi Arabia)": return "ar-SA"; case "Arabic (Syria)": return "ar-SY"; case "Arabic (Tunisia)": return "ar-TN"; case "Arabic (Yemen)": return "ar-YE"; case "Azerbaijani (Latin, Azerbaijan)": return "az-AZ"; case "Bulgarian (Bulgaria)": return "bg-BG"; case "Bengali (India)": return "bn-IN"; case "Bosnian (Bosnia and Herzegovina)": return "bs-BA"; case "Catalan": return "ca-ES"; case "Czech (Czechia)": return "cs-CZ"; case "Welsh (United Kingdom)": return "cy-GB"; case "Danish (Denmark)": return "da-DK"; case "German (Austria)": return "de-AT"; case "German (Switzerland)": return "de-CH"; case "German": case "German (Germany)": return "de-DE"; case "Greek (Greece)": return "el-GR"; case "English (Australia)": return "en-AU"; case "English (Canada)": return "en-CA"; case "English (United Kingdom)": return "en-GB"; case "English (Ghana)": return "en-GH"; case "English (Hong Kong SAR)": return "en-HK"; case "English (Ireland)": return "en-IE"; case "English (India)": return "en-IN"; case "English (Kenya)": return "en-KE"; case "English (Nigeria)": return "en-NG"; case "English (New Zealand)": return "en-NZ"; case "English (Philippines)": return "en-PH"; case "English (Singapore)": return "en-SG"; case "English (Tanzania)": return "en-TZ"; case "English": case "English (United States)": return "en-US"; case "English (South Africa)": return "en-ZA"; case "Spanish (Argentina)": return "es-AR"; case "Spanish (Bolivia)": return "es-BO"; case "Spanish (Chile)": return "es-CL"; case "Spanish (Colombia)": return "es-CO"; case "Spanish (Costa Rica)": return "es-CR"; case "Spanish (Cuba)": return "es-CU"; case "Spanish (Dominican Republic)": return "es-DO"; case "Spanish (Ecuador)": return "es-EC"; case "Spanish (Spain)": return "es-ES"; case "Spanish (Equatorial Guinea)": return "es-GQ"; case "Spanish (Guatemala)": return "es-GT"; case "Spanish (Honduras)": return "es-HN"; case "Spanish": case "Spanish (Mexico)": return "es-MX"; case "Spanish (Nicaragua)": return "es-NI"; case "Spanish (Panama)": return "es-PA"; case "Spanish (Peru)": return "es-PE"; case "Spanish (Puerto Rico)": return "es-PR"; case "Spanish (Paraguay)": return "es-PY"; case "Spanish (El Salvador)": return "es-SV"; case "Spanish (United States)": return "es-US"; case "Spanish (Uruguay)": return "es-UY"; case "Spanish (Venezuela)": return "es-VE"; case "Estonian (Estonia)": return "et-EE"; case "Basque": return "eu-ES"; case "Persian (Iran)": return "fa-IR"; case "Finnish (Finland)": return "fi-FI"; case "Filipino (Philippines)": return "fil-PH"; case "French (Belgium)": return "fr-BE"; case "French (Canada)": return "fr-CA"; case "French (Switzerland)": return "fr-CH"; case "French": case "French (France)": return "fr-FR"; case "Irish (Ireland)": return "ga-IE"; case "Galician": return "gl-ES"; case "Gujarati (India)": return "gu-IN"; case "Hebrew": case "Hebrew (Israel)": return "he-IL"; case "Hindi (India)": return "hi-IN"; case "Croatian (Croatia)": return "hr-HR"; case "Hungarian (Hungary)": return "hu-HU"; case "Armenian (Armenia)": return "hy-AM"; case "Indonesian (Indonesia)": return "id-ID"; case "Icelandic (Iceland)": return "is-IS"; case "Italian (Switzerland)": return "it-CH"; case "Italian": case "Italian (Italy)": return "it-IT"; case "Japanese": case "Japanese (Japan)": return "ja-JP"; case "Javanese (Latin, Indonesia)": return "jv-ID"; case "Georgian (Georgia)": return "ka-GE"; case "Kazakh (Kazakhstan)": return "kk-KZ"; case "Khmer (Cambodia)": return "km-KH"; case "Kannada (India)": return "kn-IN"; case "Korean": case "Korean (Korea)": return "ko-KR"; case "Lao (Laos)": return "lo-LA"; case "Lithuanian (Lithuania)": return "lt-LT"; case "Latvian (Latvia)": return "lv-LV"; case "Macedonian (North Macedonia)": return "mk-MK"; case "Malayalam (India)": return "ml-IN"; case "Mongolian (Mongolia)": return "mn-MN"; case "Marathi (India)": return "mr-IN"; case "Malay (Malaysia)": return "ms-MY"; case "Maltese (Malta)": return "mt-MT"; case "Burmese (Myanmar)": return "my-MM"; case "Norwegian Bokmål (Norway)": return "nb-NO"; case "Nepali (Nepal)": return "ne-NP"; case "Dutch": case "Dutch (Belgium)": return "nl-BE"; case "Dutch (Netherlands)": return "nl-NL"; case "Punjabi (India)": return "pa-IN"; case "Polish (Poland)": return "pl-PL"; case "Pashto (Afghanistan)": return "ps-AF"; case "Portuguese": case "Portuguese (Brazil)": return "pt-BR"; case "Portuguese (Portugal)": return "pt-PT"; case "Romanian (Romania)": return "ro-RO"; case "Russian": case "Russian (Russia)": return "ru-RU"; case "Sinhala (Sri Lanka)": return "si-LK"; case "Slovak (Slovakia)": return "sk-SK"; case "Slovenian (Slovenia)": return "sl-SI"; case "Somali (Somalia)": return "so-SO"; case "Albanian (Albania)": return "sq-AL"; case "Serbian (Cyrillic, Serbia)": return "sr-RS"; case "Swedish (Sweden)": return "sv-SE"; case "Swahili (Kenya)": return "sw-KE"; case "Swahili (Tanzania)": return "sw-TZ"; case "Tamil (India)": return "ta-IN"; case "Telugu (India)": return "te-IN"; case "Thai (Thailand)": return "th-TH"; case "Turkish": case "Turkish (Türkiye)": return "tr-TR"; case "Ukrainian (Ukraine)": return "uk-UA"; case "Urdu (India)": return "ur-IN"; case "Uzbek (Latin, Uzbekistan)": return "uz-UZ"; case "Vietnamese (Vietnam)": return "vi-VN"; case "Chinese (Wu, Simplified)": return "wuu-CN"; case "Chinese (Cantonese, Simplified)": return "yue-CN"; case "Chinese": case "Chinese (Pinyin)": case "Chinese (Hanzi)": case "Chinese (Mandarin, Simplified)": return "zh-CN"; case "Chinese (Jilu Mandarin, Simplified)": return "zh-CN-shandong"; case "Chinese (Southwestern Mandarin, Simplified)": return "zh-CN-sichuan"; case "Chinese (Cantonese, Traditional)": return "zh-HK"; case "Chinese (Taiwanese Mandarin, Traditional)": return "zh-TW"; case "Zulu (South Africa)": return "zu-ZA"; default: return "en-US"; } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/deepgram/SpeechRecDeepgram.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.deepgram; import android.content.Context; import android.util.Log; import com.teamopensmartglasses.augmentoslib.events.SpeechRecOutputEvent; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.SpeechRecFramework; import org.greenrobot.eventbus.EventBus; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.WebSocket; import okhttp3.WebSocketListener; import okio.ByteString; public class SpeechRecDeepgram extends SpeechRecFramework { // private static final String BASE_URL = "wss://api.deepgram.com/v1/listen?encoding=linear16&sample_rate=16000&diarize=false&interim_results=true&smart_format=true&utterance_end_ms=1200&punctuate=true"; //&filler_words=true"; private static final String BASE_URL = "wss://api.deepgram.com/v1/listen?encoding=linear16&sample_rate=16000&diarize=false&interim_results=true"; //&filler_words=true"; private static final String API_KEY = "3d445184e814ba545ad508c24f08b0f5e2645c09"; private WebSocket webSocket; public String TAG = "WearableAi_SpeechRecDeepgram"; private Context mContext; private String currentLanguageCode; public SpeechRecDeepgram(Context context, String languageLocale) { this.mContext = context; initLanguageLocale(languageLocale); } @Override public void start() { Log.d(TAG, "starting Deepgram"); initializeWebSocket(); } @Override public void destroy() { Log.d(TAG, "destroying Deepgram"); if (webSocket != null) { webSocket.close(1000, "Closing Connection"); } } @Override public void ingestAudioChunk(byte[] audioChunk) { if (webSocket != null) { webSocket.send(ByteString.of(audioChunk)); } } private void initializeWebSocket() { Log.d(TAG, "init socket Deepgram"); OkHttpClient client = new OkHttpClient(); String url = BASE_URL + "&language=" + currentLanguageCode; Request request = new Request.Builder() .url(url) .addHeader("Authorization", "Token " + API_KEY) .build(); webSocket = client.newWebSocket(request, new WebSocketListener() { @Override public void onOpen(WebSocket webSocket, Response response) { Log.d(TAG, "opened Deepgram"); } @Override public void onMessage(WebSocket webSocket, String text) { parseDeepgramMessage(text); } @Override public void onClosing(WebSocket webSocket, int code, String reason) { webSocket.close(1000, null); Log.d(TAG, "got closing Deepgram"); } @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { Log.d(TAG, "failure in Deepgram"); } }); client.dispatcher().executorService().shutdown(); } public void parseDeepgramMessage(String jsonString) { try { JSONObject obj = new JSONObject(jsonString); String type = obj.getString("type"); JSONArray channelIndex = obj.getJSONArray("channel_index"); double duration = obj.getDouble("duration"); double start = obj.getDouble("start"); boolean isFinal = obj.getBoolean("is_final"); boolean speechFinal = obj.getBoolean("speech_final"); JSONObject channel = obj.getJSONObject("channel"); JSONArray alternatives = channel.getJSONArray("alternatives"); JSONObject firstAlternative = alternatives.getJSONObject(0); String transcript = firstAlternative.getString("transcript"); double confidence = firstAlternative.getDouble("confidence"); JSONObject metadata = obj.getJSONObject("metadata"); String requestId = metadata.getString("request_id"); JSONObject modelInfo = metadata.getJSONObject("model_info"); String modelName = modelInfo.getString("name"); String modelVersion = modelInfo.getString("version"); String modelArch = modelInfo.getString("arch"); String modelUuid = metadata.getString("model_uuid"); boolean fromFinalize = obj.getBoolean("from_finalize"); if (!transcript.equals("") && !transcript.equals(" ") && transcript != null) { EventBus.getDefault().post(new SpeechRecOutputEvent(transcript, (long) start, isFinal)); } } catch (JSONException e) { e.printStackTrace(); } } private void initLanguageLocaleDefault() { currentLanguageCode = "en-US"; } private void initLanguageLocale(String localeString) { switch (localeString) { case "Bulgarian": currentLanguageCode = "bg"; break; case "Catalan": currentLanguageCode = "ca"; break; case "Chinese (Pinyin)": case "Chinese (Hanzi)": case "Chinese (Simplified)": case "Mandarin Simplified": currentLanguageCode = "zh"; break; case "Czech": currentLanguageCode = "cs"; break; case "Danish": currentLanguageCode = "da"; break; case "Dutch": currentLanguageCode = "nl"; break; case "English": currentLanguageCode = "en"; break; case "Estonian": currentLanguageCode = "et"; break; case "Finnish": currentLanguageCode = "fi"; break; case "French": currentLanguageCode = "fr"; break; case "German": currentLanguageCode = "de"; break; case "Greek": currentLanguageCode = "el"; break; case "Hindi": currentLanguageCode = "hi"; break; case "Hungarian": currentLanguageCode = "hu"; break; case "Indonesian": currentLanguageCode = "id"; break; case "Italian": currentLanguageCode = "it"; break; case "Japanese": currentLanguageCode = "ja"; break; case "Korean": currentLanguageCode = "ko"; break; case "Latvian": currentLanguageCode = "lv"; break; case "Lithuanian": currentLanguageCode = "lt"; break; case "Malay": currentLanguageCode = "ms"; break; case "Norwegian": currentLanguageCode = "no"; break; case "Polish": currentLanguageCode = "pl"; break; case "Portuguese": currentLanguageCode = "pt"; break; case "Romanian": currentLanguageCode = "ro"; break; case "Russian": currentLanguageCode = "ru"; break; case "Slovak": currentLanguageCode = "sk"; break; case "Spanish": currentLanguageCode = "es"; break; case "Swedish": currentLanguageCode = "sv"; break; case "Thai": currentLanguageCode = "th"; break; case "Turkish": currentLanguageCode = "tr"; break; case "Ukrainian": currentLanguageCode = "uk"; break; case "Vietnamese": currentLanguageCode = "vi"; break; default: currentLanguageCode = "en"; break; } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/SpeechRecGoogle.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google; import static com.google.audio.asr.SpeechRecognitionModelOptions.SpecificModel.DICTATION_DEFAULT; import static com.google.audio.asr.SpeechRecognitionModelOptions.SpecificModel.VIDEO; import static com.google.audio.asr.TranscriptionResultFormatterOptions.TranscriptColoringStyle.NO_COLORING; import android.content.Context; import android.util.Log; import com.google.audio.CodecAndBitrate; import com.google.audio.asr.CloudSpeechSessionParams; import com.google.audio.asr.CloudSpeechStreamObserverParams; import com.google.audio.asr.SpeechRecognitionModelOptions; import com.google.audio.asr.TranscriptionResultFormatterOptions; import com.teamopensmartglasses.smartglassesmanager.SmartGlassesAndroidService; import com.teamopensmartglasses.augmentoslib.events.SpeechRecOutputEvent; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.SpeechRecFramework; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.RepeatingRecognitionSession; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SafeTranscriptionResultFormatter; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.TranscriptionResultUpdatePublisher; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.NetworkConnectionChecker; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.gcloudspeech.CloudSpeechSessionFactory; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.vad.VadGateSpeechPolicy; import org.greenrobot.eventbus.EventBus; public class SpeechRecGoogle extends SpeechRecFramework { public String TAG = "WearableAi_SpeechRecGoogle"; private Context mContext; public SpeechRecGoogle(Context mContext) { this.mContext = mContext; initLanguageLocaleDefault(); // EventBus.getDefault().register(this); } public SpeechRecGoogle(Context mContext, String languageLocale) { this.mContext = mContext; initLanguageLocale(languageLocale); // EventBus.getDefault().register(this); } // @Subscribe // public void onGoogleAudioChunkNewEvent(GoogleAudioChunkNewEvent receivedEvent) { // } @Override public void ingestAudioChunk(byte[] audioChunk) { recognizer.processAudioBytes(audioChunk); } @Override public void start(){ //start text to speech constructRepeatingRecognitionSession(); recognizer.init(1024*3); } @Override public void destroy(){ if (recognizer != null) { recognizer.unregisterCallback(transcriptUpdater); networkChecker.unregisterNetworkCallback(); recognizer.stop(); } } private int currentLanguageCodePosition; private String currentLanguageCode; // This class was intended to be used from a thread where timing is not critical (i.e. do not // call this in a system audio callback). Network calls will be made during all of the functions // that RepeatingRecognitionSession inherits from SampleProcessorInterface. private RepeatingRecognitionSession recognizer; private NetworkConnectionChecker networkChecker; private final TranscriptionResultUpdatePublisher transcriptUpdater = (formattedTranscript, updateType) -> { //post the event bus event if (updateType == TranscriptionResultUpdatePublisher.UpdateType.TRANSCRIPT_FINALIZED){ // Log.d(TAG, "GOT FINAL TRANSCRIPT: " + formattedTranscript.toString()); EventBus.getDefault().post(new SpeechRecOutputEvent(formattedTranscript.toString(), System.currentTimeMillis(), true)); } else { EventBus.getDefault().post(new SpeechRecOutputEvent(formattedTranscript.toString(), System.currentTimeMillis(), false)); } }; private void initLanguageLocaleDefault() { // The default locale is en-US. currentLanguageCode = "en-US"; } private void initLanguageLocale(String localeString) { if (localeString.equals("English")) { currentLanguageCode = "en-US"; } else if (localeString.equals("Russian")) { currentLanguageCode = "ru-RU"; } else if (localeString.equals("Japanese")) { currentLanguageCode = "ja-JP"; } else if (localeString.contains("Chinese")) { currentLanguageCode = "zh"; } else if (localeString.equals("Spanish")) { currentLanguageCode = "es-MX"; } else if (localeString.equals("Hebrew")) { currentLanguageCode = "iw-IL"; } else if (localeString.equals("Dutch")) { currentLanguageCode = "nl-NL"; } else if(localeString.equals("French")){ currentLanguageCode = "fr-FR"; }else if (localeString.equals("German")) { currentLanguageCode = "de-DE"; } else if (localeString.equals("Arabic")) { currentLanguageCode = "ar-AR"; } else if (localeString.equals("Korean")) { currentLanguageCode = "ko-KR"; } else if (localeString.equals("Italian")) { currentLanguageCode = "it-IT"; } else if (localeString.equals("Turkish")) { currentLanguageCode = "tr-TR"; } else if (localeString.equals("Portuguese")) { currentLanguageCode = "pt-PT"; } else { currentLanguageCode = "en-US"; } } private void constructRepeatingRecognitionSession() { SpeechRecognitionModelOptions options = SpeechRecognitionModelOptions.newBuilder() .setLocale(currentLanguageCode) // As of 7/18/19, Cloud Speech's video model supports en-US only. .setModel(currentLanguageCode.equals("en-US") ? VIDEO : DICTATION_DEFAULT) //this is overwritten to use 'latest_long' in cloud/CloudSpeechSession.java .build(); CloudSpeechSessionParams cloudParams = CloudSpeechSessionParams.newBuilder() .setObserverParams( CloudSpeechStreamObserverParams.newBuilder().setRejectUnstableHypotheses(false)) .setFilterProfanity(false) .setEncoderParams( CloudSpeechSessionParams.EncoderParams.newBuilder() .setEnableEncoder(true) .setAllowVbr(true) .setCodec(CodecAndBitrate.OGG_OPUS_BITRATE_32KBPS)) .build(); networkChecker = new NetworkConnectionChecker(mContext); networkChecker.registerNetworkCallback(); // There are lots of options for formatting the text. These can be useful for debugging // and visualization, but it increases the effort of reading the transcripts. TranscriptionResultFormatterOptions formatterOptions = TranscriptionResultFormatterOptions.newBuilder() .setTranscriptColoringStyle(NO_COLORING) .build(); RepeatingRecognitionSession.Builder recognizerBuilder = RepeatingRecognitionSession.newBuilder() .setSpeechSessionFactory(new CloudSpeechSessionFactory(cloudParams, SmartGlassesAndroidService.getApiKey(mContext))) .setSampleRateHz(16000) .setTranscriptionResultFormatter(new SafeTranscriptionResultFormatter(formatterOptions)) .setSpeechRecognitionModelOptions(options) .setSpeechDetectionPolicy(new VadGateSpeechPolicy(mContext)) .setNetworkConnectionChecker(networkChecker); recognizer = recognizerBuilder.build(); recognizer.registerCallback(transcriptUpdater, TranscriptionResultUpdatePublisher.ResultSource.MOST_RECENT_SEGMENT); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/AlwaysSameSpeakerIDLabeler.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import com.google.audio.SpeakerIdInfo; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.SpeakerIDLabeler; import org.joda.time.Instant; /** A diarizer that always reports the same speaker. */ public class AlwaysSameSpeakerIDLabeler implements SpeakerIDLabeler { private final SpeakerIdInfo fixedInfo; public AlwaysSameSpeakerIDLabeler(SpeakerIdInfo fixedInfo) { this.fixedInfo = fixedInfo; } @Override public void setReferenceTimestamp(Instant now) {} @Override public SpeakerIdInfo getSpeakerIDForTimeInterval(Instant start, Instant end) { return fixedInfo; } @Override public void init(int blockSizeSamples) {} @Override public void clearSpeakerIDTimestamps() {} @Override public void reset() {} @Override public void processAudioBytes(byte[] bytes, int offset, int length) {} @Override public void stop() {} } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/AlwaysSpeechPolicy.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; /** A speech detector that always reports hearing speech. */ public class AlwaysSpeechPolicy implements SpeechDetectionPolicy { public AlwaysSpeechPolicy() {} @Override public boolean shouldPassAudioToRecognizer() { return true; } @Override public void init(int blockSizeSamples) {} @Override public void reset() {} @Override public void processAudioBytes(byte[] bytes, int offset, int length) {} @Override public void stop() {} } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/RepeatingRecognitionSession.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import android.text.Spanned; import com.google.audio.SpeakerIdInfo; import com.google.audio.asr.SpeechRecognitionModelOptions; import com.google.audio.asr.TranscriptionResult; import com.google.common.base.Optional; import com.google.common.flogger.FluentLogger; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.CircularByteBuffer; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.NetworkConnectionChecker; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.SampleProcessorInterface; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.SpeakerIDLabeler; import org.joda.time.Duration; import org.joda.time.Instant; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * Repeatedly runs recognition sessions, starting a new session whenever one terminates, until * stopped. * *

Incoming is speech is timestamped as time since epoch in milliseconds. * *

In between sessions, some buffering is done, but if the internal session is closed for more * than SECONDS_TO_STORE_BETWEEN_SESSIONS, audio will be lost. * *

This class was intended to be used from a thread where timing is not critical (i.e. do not * call this in a system audio callback). Network calls may be made during all of the functions * inherited from SampleProcessorInterface. * *

Results delivered via a TranscriptionResultUpdatePublisher are done so asynchronously. All * public functions that are not a part of the SampleProcessorInterface API may be called from any * thread at any time. * *

TranscriptionResultUpdatePublisher callbacks are delivered from a thread on a separate thread * pool. You will never get two callbacks to the same TranscriptionResultUpdatePublisher instance at * the same time. */ // Threading notes: // // Recognition thread: // This thread is owned by whatever system is providing the class with audio (one that treats this // generically as a SampleProcessorInterface, interacting only with the init(), processAudioBytes(), // and stop() methods). This is the thread that is doing most of the work of the class (session // management, audio buffering and processing, network checks, etc.). Be aware that it can make // network calls and do other expensive actions that should not be placed in a system audio // callback. // // Note that your audio engine should never call init(), processAudioBytes(), and stop() // concurrently. // // Results thread: // This thread is not exposed outside of this class. It is controlled by the CloudSpeechSession and // alerts the TranscriptionResultReceiver when the recognition has a result from the server. It // posts requests to the recognition thread via that 'requests' member below. If the speech session // implementation is single threaded, this thread and the recognition thread are the same thread. // // Other threads: // Public methods other than init(), processAudioBytes(), and stop() may be called from threads // other than the recognition thread. They are thread-safe. public class RepeatingRecognitionSession implements SampleProcessorInterface { /* ---------------- BEGIN: MEMBERS THAT ARE SHARED ACROSS MULTIPLE THREADS ------------------ */ private static final FluentLogger logger = FluentLogger.forEnclosingClass(); // All threads but the recognition thread may post to this queue, only the recognition thread // will read from it. private final ConcurrentLinkedQueue requests = new ConcurrentLinkedQueue<>(); // Shared between client threads and recognizer thread. private final AtomicBoolean repeatedSessionIsInitialized = new AtomicBoolean(false); private final AtomicReference modelOptions = new AtomicReference<>(); private final ConcurrentLinkedQueue callbackRefs; private final TranscriptionErrorPublisher transcriptionErrorPublisher; // The client may have retained a reference to the formatter, so it is assumed to be accessible // from multiple threads. However, it is a thread-safe object. private final SafeTranscriptionResultFormatter resultFormatter; /* ----------------- END: MEMBERS THAT ARE SHARED ACROSS MULTIPLE THREADS ------------------- */ /* --------------- BEGIN: MEMBERS THAT ARE ACCESSED ONLY FROM RESULTS THREAD ---------------- */ /** An interface for posting requests to the RepeatingRecognitionSession. */ public interface PostHandler { void post(RequestForRecognitionThread request); } private final PostHandler postHandler; private final SpeechSessionListener speechSessionListener; /* ---------------- END: MEMBERS THAT ARE ACCESSED ONLY FROM RESULTS THREAD ----------------- */ /* ------------- BEGIN: MEMBERS THAT ARE ACCESSED ONLY FROM RECOGNITION THREAD -------------- */ /** * Used to keep track of the current session. This is not only used for numbering incoming * sessions, but also to prevent old sessions from updating the state of this object. Old sessions * may persist and send results back to RepeatingRecognitionSession after a call to reset() or * after stop() and init() are called in quick succession. Sessions IDs will be monotonically * increasing, but are not guaranteed to be contiguous. */ private int currentSessionID = -1; public static final Duration SECONDS_TO_STORE_BETWEEN_SESSIONS = Duration.standardSeconds(10); private static final int BYTES_PER_SAMPLE = 2; // Members related to session management. private SpeechSession currentSession; private final SpeechSessionFactory sessionFactory; private final int sampleRateHz; private int chunkSizeSamples; private boolean isStopped = false; private boolean okToTerminateSession = false; // Some variables to facilitate buffering between sessions. private static int maxNumSamplesToStoreBetweenSessions; private CircularByteBuffer leftoverBytes; private byte[] leftoverBytesAllocation; // Exists to avoid repeated allocations. private CircularByteBuffer.Reader leftoverBytesReader; /** * Allows the RepeatingRecognitionSession to stall session creation when there is a network error * until connection is regained. If not provided, a short delay will happen after connection loss * to prevent a rapid recreation of sessions. */ private final NetworkConnectionChecker networkCheck; private boolean hadNetworkConnectionError = false; private Instant lastInitSessionTimestampWithoutNetworkChecker = new Instant(0); static final Duration RECREATE_SESSION_IF_NO_NETWORKCHECKER_DURATION = Duration.standardSeconds(1); private final SpeechDetectionPolicy speechDetector; private final SpeakerIDLabeler diarizer; /** Passes results back to registered listeners. */ private final ExecutorService resultsDeliveryService; /** * Keeps track of time that the last session ended. This will be used to log how long reconnection * takes. */ private Optional endSessionRequestTime = Optional.absent(); /* -------------- END: MEMBERS THAT ARE ACCESSED ONLY FROM RECOGNITION THREAD --------------- */ /* * A specialized reference for {@link TranscriptionResultUpdatePublisher} that allows us to fix * the {@link TranscriptionResultUpdatePublisher.ResultSource} which the callback expect to * handle. */ private static class TranscriptionResultPublisherReference extends WeakReference { final TranscriptionResultUpdatePublisher.ResultSource source; public TranscriptionResultPublisherReference( TranscriptionResultUpdatePublisher referent, TranscriptionResultUpdatePublisher.ResultSource source) { super(referent); this.source = source; } } private RepeatingRecognitionSession(Builder builder) { this.postHandler = (request) -> requests.add(request); this.speechSessionListener = new TranscriptionResultReceiver(postHandler); this.resultFormatter = builder.resultFormatter; this.sampleRateHz = builder.sampleRateHz; this.sessionFactory = builder.sessionFactory; this.modelOptions.set(builder.modelOptions); this.networkCheck = builder.networkCheck; this.speechDetector = builder.speechDetector; this.diarizer = builder.diarizer; this.callbackRefs = builder.callbackRefs; this.resultsDeliveryService = builder.resultsDeliveryService; this.transcriptionErrorPublisher = builder.transcriptionErrorPublisher; maxNumSamplesToStoreBetweenSessions = (int) Math.ceil(SECONDS_TO_STORE_BETWEEN_SESSIONS.getStandardSeconds() * sampleRateHz); } public static Builder newBuilder() { return new Builder(); } // Should only be called on the recognition thread. See threading notes above. @Override public void init(int chunkSizeSamples) { checkArgument(chunkSizeSamples > 0); this.chunkSizeSamples = chunkSizeSamples; speechDetector.init(chunkSizeSamples); diarizer.init(chunkSizeSamples); diarizer.setReferenceTimestamp(Instant.now()); this.leftoverBytes = new CircularByteBuffer(maxNumSamplesToStoreBetweenSessions * BYTES_PER_SAMPLE); this.leftoverBytesAllocation = new byte[leftoverBytes.getCapacity()]; this.leftoverBytesReader = leftoverBytes.newReader(); isStopped = false; okToTerminateSession = false; // Create the first session. currentSession = sessionFactory.create(speechSessionListener, sampleRateHz); repeatedSessionIsInitialized.set(true); } // Should only be called on the recognition thread. See threading notes above. @Override public void processAudioBytes(byte[] samples, int offset, int length) { if (!repeatedSessionIsInitialized.get()) { throw new IllegalStateException("processAudioBytes() called prior to initialization!"); } if (isStopped) { throw new IllegalStateException("processAudioBytes() called while stopped!"); } // Ignoring thread safety issues, it would be ideal to run handlePostedActions() endlessly in // another thread. To keep everything on the same thread, we run it in this function first // at the beginning to process reset events as soon as possible and again at the end to process // results as soon as their generated (in practice, this is mostly useful during testing). handlePostedActions(); speechDetector.processAudioBytes(samples, offset, length); diarizer.processAudioBytes(samples, offset, length); // Restart the session when necessary. if (okToTerminateSession) { logger.atInfo().log( "Creating a new session. Reconnection timer: %s", getReconnectionTimerValue()); currentSession = sessionFactory.create(speechSessionListener, sampleRateHz); okToTerminateSession = false; } // If we need network, but it is unavailable, put the samples in leftovers. boolean networkRequirementsMet = !currentSession.requiresNetworkConnection() || isNetworkAvailable(); if (!networkRequirementsMet) { storeSamplesInLeftovers(samples, offset, length, false); // Stop the session when network is lost. if (currentSession.isInitialized()) { logger.atInfo().log( "Online Session #%d abandoned due to lack of network connection.", currentSession.sessionID()); requestCurrentSessionEnd(); } return; } hadNetworkConnectionError = false; // If there is no speech, end the session, and don't try to process data. if (!speechDetector.shouldPassAudioToRecognizer()) { // Buffer the speech so that when we reconnect, even a late speech detection will cause some // of the buffered audio to get to the server. If we drop samples we don't need to log because // we know it does not contain speech. storeSamplesInLeftovers(samples, offset, length, true); if (currentSession.isInitialized()) { // logger.atInfo().log( // "Session #%d ending due to lack of detected speech.", currentSession.sessionID()); requestCurrentSessionEnd(); } return; } // Initialize the session. if (!currentSession.isInitialized()) { // Get the reference to the model so that the log and the session see the same version. SpeechRecognitionModelOptions model = modelOptions.get(); currentSessionID++; // logger.atInfo().log( // "Starting a Session #%d in language `%s`.", currentSessionID, model.getLocale()); currentSession.init(model, chunkSizeSamples, currentSessionID); } tryToProcessLeftovers(); // If the session can take requests, send samples. Otherwise, put them into the leftover queue. if (currentSession.processAudioBytes(samples, offset, length)) { stopReconnectionTimer(); } else { storeSamplesInLeftovers(samples, offset, length, false); } handlePostedActions(); } /** * Terminate the current session. Any results from the server after a call to stop() are not * guaranteed to arrive. */ // Should only be called only from the MicManager on the recognition thread as a // SampleProcessorInterface. @Override public void stop() { // Handle any requests that have happened prior to now. handlePostedActions(); isStopped = true; speechDetector.stop(); diarizer.stop(); if (currentSession.isInitialized()) { // logger.atInfo().log( // "Session #%d abandoned due to repeated session ending.", currentSession.sessionID()); abandonCurrentSession(); } repeatedSessionIsInitialized.set(false); } /** * Restarts recognition, discarding the state of the currently active session. Request is * performed asynchronously, so this function may be called from any thread at any point during * the session. * *

Results generated after the asynchronous reset will not arrive. */ // May be called from any thread. public void reset() { reset(false); } private void reset(boolean clearTranscript) { if (!repeatedSessionIsInitialized.get()) { return; } // logger.atInfo().log( // "Session #%d scheduled to be abandoned due to call to reset().", // currentSession.sessionID()); requests.add( RequestForRecognitionThread.newBuilder() .setAction( clearTranscript ? RequestForRecognitionThread.Action.RESET_SESSION_AND_CLEAR_TRANSCRIPT : RequestForRecognitionThread.Action.RESET_SESSION) .build()); } /** * Restarts recognition, discarding the state of the currently active session. Request is * performed asynchronously, so this function may be called from any thread at any point during * the session. Clears returned transcript. The caller will know that * the reset has been completed when a TRANSCRIPT_CLEARED is received through the listener. */ // May be called from any thread. public void resetAndClearTranscript() { reset(true); } /** * Sets the modelOptions, which may include a language change or usage of a different model. * Session management is performed asynchronously, so this function may be called from any thread * at any point during the session. */ // May be called from any thread. public void setModelOptions(SpeechRecognitionModelOptions modelOptions) { this.modelOptions.set(modelOptions); // logger.atInfo().log("Session scheduled to be ended due to model options change."); requests.add( RequestForRecognitionThread.newBuilder() .setAction(RequestForRecognitionThread.Action.REQUEST_TO_END_SESSION) .build()); } /** Gets the modelOptions, which may include a language change or usage of a different model. */ // May be called from any thread. public SpeechRecognitionModelOptions getModelOptions() { return modelOptions.get(); } // Must be called prior to init() or after stop(). public void registerCallback( TranscriptionResultUpdatePublisher callback, TranscriptionResultUpdatePublisher.ResultSource source) { checkNotNull(callback); Iterator iterator = callbackRefs.iterator(); while (iterator.hasNext()) { if (callback.equals(iterator.next().get())) { throw new RuntimeException("Listener is already registered."); } } callbackRefs.add(new TranscriptionResultPublisherReference(callback, source)); } // Must be called prior to init() or after stop(). public void unregisterCallback(TranscriptionResultUpdatePublisher callback) { checkNotNull(callback); Iterator iterator = callbackRefs.iterator(); while (iterator.hasNext()) { if (callback.equals(iterator.next().get())) { iterator.remove(); } } } /** * Pulls requests off of the queue and performs them. This is only to be called from the thread * that is calling init(), processAudioBytes(), and stop(). * *

Internal notes: None of the tasks performed while emptying the request queue should make a * call to handlePostedActions() or post new requests, as this may result in results being * processed out of order. */ private void handlePostedActions() { RequestForRecognitionThread request = requests.poll(); while (request != null) { if (request.hasSessionID() && request.sessionID() < currentSessionID) { // Completely ignore results for sessions that have been abandoned. // logger.atInfo().log("Old event from Session #%d discarded.", request.sessionID()); request = requests.poll(); continue; } switch (request.action()) { case HANDLE_NETWORK_CONNECTION_FATAL_ERROR: // logger.atInfo().log("Closing Session #%d due to network error.", request.sessionID()); finalizeLeftoverHypothesis(); okToTerminateSession = true; processError(request.getErrorCause()); startReconnectionTimer(); break; case HANDLE_NON_NETWORK_CONNECTION_FATAL_ERROR: // logger.atInfo().log("Closing Session #%d due to non-network error.", request.sessionID()); hadNetworkConnectionError = true; finalizeLeftoverHypothesis(); okToTerminateSession = true; processError(request.getErrorCause()); startReconnectionTimer(); break; case POST_RESULTS: // logger.atInfo().log( // "Session #%d received result (final = %b).", // request.sessionID(), request.requestIsFinal()); processResult(request.result(), request.requestIsFinal()); break; case OK_TO_TERMINATE_SESSION: // logger.atInfo().log("Terminating Session #%d cleanly.", request.sessionID()); okToTerminateSession = true; startReconnectionTimer(); break; case REQUEST_TO_END_SESSION: requestCurrentSessionEnd(); break; case RESET_SESSION: resetInternal(); break; case RESET_SESSION_AND_CLEAR_TRANSCRIPT: resetInternal(); resultFormatter.reset(); sendTranscriptResultUpdated( TranscriptionResultUpdatePublisher.UpdateType.TRANSCRIPT_CLEARED); break; case NO_ACTION: break; } request = requests.poll(); } } private void processError(Throwable errorCause) { if (transcriptionErrorPublisher != null) { transcriptionErrorPublisher.onError(errorCause); } } private void resetInternal() { speechDetector.reset(); if (currentSession.isInitialized()) { // logger.atInfo().log( // "Session #%d abandoned due to call to reset().", currentSession.sessionID()); abandonCurrentSession(); } } private void requestCurrentSessionEnd() { if (repeatedSessionIsInitialized.get() && currentSession.isInitialized()) { currentSession.requestCloseSession(); } } private void abandonCurrentSession() { finalizeLeftoverHypothesis(); requestCurrentSessionEnd(); // By incrementing the session ID here, we are preventing results from the old session from // being processed. currentSessionID++; okToTerminateSession = true; } private void tryToProcessLeftovers() { // Process stored samples, if there are any. int numLeftoverBytes = leftoverBytesReader.availableBytes(); if (numLeftoverBytes > 0) { leftoverBytesReader.peek(leftoverBytesAllocation, 0, numLeftoverBytes); if (currentSession.processAudioBytes(leftoverBytesAllocation, 0, numLeftoverBytes)) { stopReconnectionTimer(); leftoverBytes.reset(); // Readers get reset. } } } private void storeSamplesInLeftovers( byte[] samples, int offset, int length, boolean droppingSamplesIsIntended) { // If we fail this, it means we passed many seconds of audio at once. This should never happen // under normal streaming conditions. checkArgument(length < leftoverBytes.getCapacity()); int numLeftoverBytes = leftoverBytesReader.availableBytes(); if (numLeftoverBytes + length > leftoverBytes.getCapacity()) { if (!droppingSamplesIsIntended) { // logger.atSevere().atMostEvery(5, TimeUnit.SECONDS).log( // "Dropped audio between sessions. [atMostEvery 5s]"); } leftoverBytesReader.advance((numLeftoverBytes + length) - leftoverBytes.getCapacity()); } leftoverBytes.write(samples, offset, length); } /** * Check reconnect timeout to prevent connecting fail repeatedly in a short time. * * @return true if establishing connection is allowed. */ private boolean isNetworkReconnectionTimeout() { if (RECREATE_SESSION_IF_NO_NETWORKCHECKER_DURATION.isShorterThan( new Duration(lastInitSessionTimestampWithoutNetworkChecker, Instant.now()))) { // Allow to create a new session every second. lastInitSessionTimestampWithoutNetworkChecker = Instant.now(); return true; } return false; } private boolean isNetworkAvailable() { if (networkCheck != null) { return networkCheck.isConnected(); } else if (hadNetworkConnectionError) { return isNetworkReconnectionTimeout(); } else { return true; } } protected void processResult(TranscriptionResult result, boolean resultIsFinal) { speechDetector.cueEvidenceOfSpeech(); result = addSpeakerIDLabels(result); resultFormatter.setCurrentHypothesis(result); if (resultIsFinal) { resultFormatter.finalizeCurrentHypothesis(); } sendTranscriptResultUpdated( resultIsFinal ? TranscriptionResultUpdatePublisher.UpdateType.TRANSCRIPT_FINALIZED : TranscriptionResultUpdatePublisher.UpdateType.TRANSCRIPT_UPDATED); } private void finalizeLeftoverHypothesis() { if (resultFormatter.finalizeCurrentHypothesis()) { sendTranscriptResultUpdated( TranscriptionResultUpdatePublisher.UpdateType.TRANSCRIPT_FINALIZED); } } private void startReconnectionTimer() { endSessionRequestTime = Optional.of(Instant.now()); } private String getReconnectionTimerValue() { if (endSessionRequestTime.isPresent()) { Duration difference = new Duration(endSessionRequestTime.get(), Instant.now()); return "<" + difference.getMillis() / 1000.0f + "s>"; } return ""; } private void stopReconnectionTimer() { if (endSessionRequestTime.isPresent()) { String endTime = getReconnectionTimerValue(); // logger.atInfo().log("Reconnection timer stopped: %s.", endTime); } endSessionRequestTime = Optional.absent(); } private void sendTranscriptResultUpdated(TranscriptionResultUpdatePublisher.UpdateType type) { final Spanned transcript = resultFormatter.getFormattedTranscript(); final Spanned segment = resultFormatter.getMostRecentTranscriptSegment(); Iterator iterator = callbackRefs.iterator(); while (iterator.hasNext()) { TranscriptionResultPublisherReference ref = iterator.next(); final TranscriptionResultUpdatePublisher publisher = ref.get(); final TranscriptionResultUpdatePublisher.ResultSource source = ref.source; final TranscriptionResultUpdatePublisher.UpdateType typeToSend = type; if (publisher == null) { iterator.remove(); } else { resultsDeliveryService.execute( () -> { synchronized (publisher) { switch (source) { case MOST_RECENT_SEGMENT: publisher.onTranscriptionUpdate(segment, typeToSend); break; case WHOLE_RESULT: publisher.onTranscriptionUpdate(transcript, typeToSend); break; } } }); } } } /** Returns a new proto that is labeled with speaker ID information. */ TranscriptionResult addSpeakerIDLabels(TranscriptionResult result) { // We don't know whether we'll be using word level detail or not downstream, so have the // diarizer process everything. SpeakerIdInfo wholeUtteranceInfo = diarizer.getSpeakerIDForTimeInterval( TimeUtil.toInstant(result.getStartTimestamp()), TimeUtil.toInstant(result.getEndTimestamp())); List wordLevelInfo = new ArrayList<>(result.getWordLevelDetailCount()); for (TranscriptionResult.Word word : result.getWordLevelDetailList()) { wordLevelInfo.add( diarizer.getSpeakerIDForTimeInterval( TimeUtil.toInstant(word.getStartTimestamp()), TimeUtil.toInstant(word.getEndTimestamp()))); } // Protos are immutable, so to move the diarization info in, we need to make a deep copy and // fill in the word level data. return result.toBuilder() .setSpeakerInfo(wholeUtteranceInfo) .clearWordLevelDetail() .addAllWordLevelDetail( IntStream.range(0, wordLevelInfo.size()) .mapToObj( i -> result.getWordLevelDetail(i).toBuilder() .setSpeakerInfo(wordLevelInfo.get(i)) .build()) .collect(Collectors.toList())) .build(); } /** A Builder class for constructing RepeatingRecognitionSessions. */ public static class Builder { // Required. private int sampleRateHz; private SpeechSessionFactory sessionFactory; private SpeechRecognitionModelOptions modelOptions; // Optional. Note that if you don't have either a resultFormatter or a callbackRefs there is // no way to get output out of the RepeatingRecognitionSession. private SafeTranscriptionResultFormatter resultFormatter = new SafeTranscriptionResultFormatter(); private NetworkConnectionChecker networkCheck; private SpeechDetectionPolicy speechDetector = new AlwaysSpeechPolicy(); private SpeakerIDLabeler diarizer = new AlwaysSameSpeakerIDLabeler(SpeakerIdInfo.newBuilder().setSpeakerId(0).build()); private final ConcurrentLinkedQueue callbackRefs = new ConcurrentLinkedQueue<>(); private TranscriptionErrorPublisher transcriptionErrorPublisher; private ExecutorService resultsDeliveryService = Executors.newCachedThreadPool(); private Builder() {} public RepeatingRecognitionSession build() { checkArgument(sampleRateHz > 0); checkNotNull(modelOptions); checkNotNull(sessionFactory); return new RepeatingRecognitionSession(this); } public Builder setSampleRateHz(int sampleRateHz) { this.sampleRateHz = sampleRateHz; return this; } public Builder setSpeechSessionFactory(SpeechSessionFactory factory) { this.sessionFactory = factory; return this; } public Builder setSpeechRecognitionModelOptions(SpeechRecognitionModelOptions modelOptions) { this.modelOptions = modelOptions; return this; } public Builder setNetworkConnectionChecker(NetworkConnectionChecker networkCheck) { this.networkCheck = networkCheck; return this; } public Builder setTranscriptionResultFormatter(SafeTranscriptionResultFormatter formatter) { this.resultFormatter = formatter; return this; } public Builder setSpeechDetectionPolicy(SpeechDetectionPolicy speechDetector) { this.speechDetector = speechDetector; return this; } public Builder setSpeakerIDLabeler(SpeakerIDLabeler diarizer) { this.diarizer = diarizer; return this; } public Builder addTranscriptionResultCallback( TranscriptionResultUpdatePublisher callback, TranscriptionResultUpdatePublisher.ResultSource source) { checkNotNull(callback); Iterator iterator = callbackRefs.iterator(); while (iterator.hasNext()) { if (callback.equals(iterator.next().get())) { throw new RuntimeException("Listener is already registered."); } } callbackRefs.add(new TranscriptionResultPublisherReference(callback, source)); return this; } public Builder setTranscriptionErrorPublisher(TranscriptionErrorPublisher publisher) { transcriptionErrorPublisher = publisher; return this; } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/RequestForRecognitionThread.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import static com.google.common.base.Preconditions.checkArgument; import com.google.audio.asr.TranscriptionResult; /** * Actions that may be performed on the session that should be executed in the recognition thread. * *

Most requests come from a session, but they can also come from a client triggering a reset or * a model change. If the request comes from a session, it will be marked with a session ID. */ public class RequestForRecognitionThread { private static final int NO_SESSION = -1; private final Action action; private final TranscriptionResult result; private final boolean requestIsFinal; private final int sessionID; private final Throwable errorCause; /** The action that will be executed on the audio thread of the RepeatingRecognitionSession. */ public enum Action { NO_ACTION, HANDLE_NETWORK_CONNECTION_FATAL_ERROR, HANDLE_NON_NETWORK_CONNECTION_FATAL_ERROR, OK_TO_TERMINATE_SESSION, POST_RESULTS, REQUEST_TO_END_SESSION, RESET_SESSION, RESET_SESSION_AND_CLEAR_TRANSCRIPT, } private RequestForRecognitionThread(Builder builder) { this.action = builder.action; this.result = builder.result; this.requestIsFinal = builder.requestIsFinal; this.sessionID = builder.sessionID; this.errorCause = builder.errorCause; } static Builder newBuilder() { return new Builder(); } Action action() { return action; } boolean hasSessionID() { return sessionID != NO_SESSION; } // May return NO_SESSION. int sessionID() { return sessionID; } boolean requestIsFinal() { return requestIsFinal; } TranscriptionResult result() { return result; } Throwable getErrorCause() { return errorCause; } /** A Builder class for RequestForRecognitionThread objects. */ static class Builder { private Action action = Action.NO_ACTION; private int sessionID = NO_SESSION; private TranscriptionResult result = null; private boolean requestIsFinal; private Throwable errorCause = null; private Builder() {} /** Notes the action to be performed. If you don't call this, no action will be requested. */ public Builder setAction(Action action) { this.action = action; return this; } /** Tells the audio thread what the corresponding session ID is. Must be non-negative. */ public Builder setSessionID(int sessionID) { // We use a negative value to indicate NO_SESSION. Do not assign a negative ID. checkArgument(sessionID >= 0); this.sessionID = sessionID; return this; } /** Adds a finalized/nonfinalized result to the request. */ public Builder setResult(TranscriptionResult result, boolean requestIsFinal) { this.result = result; this.requestIsFinal = requestIsFinal; return this; } public RequestForRecognitionThread build() { return new RequestForRecognitionThread(this); } public Builder setErrorCause(Throwable errorCause) { this.errorCause = errorCause; return this; } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/SafeTranscriptionResultFormatter.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import android.text.Spanned; import com.google.audio.asr.TranscriptionResult; import com.google.audio.asr.TranscriptionResultFormatterOptions; import com.google.common.flogger.FluentLogger; import com.google.common.util.concurrent.SettableFuture; import org.joda.time.Duration; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.GuardedBy; /** * A thread-safe version of the TranscriptionResultFormatter. Please see * TranscriptionResultFormatter.java for notes on how to use the class. * *

Concurrency notes: These functions may be called from any thread. Note that the delay * corresponding to any particular function call depends on the length of the request queue. * *

This class implements a confined thread concurrency model where an internal service, * TranscriptionResultFormatterService, delegates requests to an instance of * TranscriptResultFormatter in an isolated thread. The thread remains open as long as there is work * to be done. If the work queue empties, the thread will close and a new one will spawn on the next * work request. */ public class SafeTranscriptionResultFormatter { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final TranscriptionResultFormatterService service; /** Protects the member, confinedThread, when the worker thread is being restarted. */ private final Object threadRestartLock = new Object(); @GuardedBy("threadRestartLock") private Thread confinedThread; /** * A request is a single job to be executed on the worker thread, confinedThread. Its members * represent the various inputs/outputs of the TranscriptionResultFormatter. */ private static class Request { // Each of these elements is only used for a subset of requests. public TranscriptionResultFormatterOptions inOptions; public TranscriptionResult inTranscriptionResult; public final SettableFuture outBoolean; public final SettableFuture outSpanned; public final SettableFuture outDuration; private final RequestType type; public Request(RequestType type) { this.type = type; outBoolean = SettableFuture.create(); outSpanned = SettableFuture.create(); outDuration = SettableFuture.create(); } } // Used to pass requests to TranscriptionResultFormatterService. private final BlockingQueue requestQueue = new ArrayBlockingQueue<>(100); private enum RequestType { SET_OPTIONS, RESET, ADD_FINALIZED_RESULT, CLEAR_CURRENT_HYPOTHESIS, FINALIZE_CURRENT_HYPOTHESIS, SET_CURRENT_HYPOTHESIS, GET_FORMATTED_TRANSCRIPT, GET_MOST_RECENT_TRANSCRIPT_SEGMENT, GET_TRANSCRIPT_DURATION, } private static final String EXECUTION_EXCEPTION_MESSAGE = "request failed."; private static final String INTERRUPTED_EXCEPTION_MESSAGE = "was interrupted."; public SafeTranscriptionResultFormatter() { this.service = new TranscriptionResultFormatterService(); } public SafeTranscriptionResultFormatter(TranscriptionResultFormatterOptions options) { this.service = new TranscriptionResultFormatterService(options); } private void ensureThreadIsRunning() { synchronized (threadRestartLock) { if (confinedThread == null) { logger.atInfo().log("Restarting formatter request queue. %s", confinedThread); confinedThread = new Thread(service); confinedThread.start(); } } } public void setOptions(TranscriptionResultFormatterOptions options) { try { Request request = new Request(RequestType.SET_OPTIONS); request.inOptions = options; requestQueue.put(request); ensureThreadIsRunning(); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log("setOptions %s", INTERRUPTED_EXCEPTION_MESSAGE); } } public void reset() { try { requestQueue.put(new Request(RequestType.RESET)); ensureThreadIsRunning(); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log("reset %s", INTERRUPTED_EXCEPTION_MESSAGE); } } public void addFinalizedResult(TranscriptionResult resultSingleUtterance) { try { Request request = new Request(RequestType.ADD_FINALIZED_RESULT); request.inTranscriptionResult = resultSingleUtterance; requestQueue.put(request); ensureThreadIsRunning(); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log( "addFinalizedResult %s", INTERRUPTED_EXCEPTION_MESSAGE); } } public void clearCurrentHypothesis() { try { requestQueue.put(new Request(RequestType.CLEAR_CURRENT_HYPOTHESIS)); ensureThreadIsRunning(); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log( "clearCurrentHypothesis %s", INTERRUPTED_EXCEPTION_MESSAGE); } } public boolean finalizeCurrentHypothesis() { try { Request request = new Request(RequestType.FINALIZE_CURRENT_HYPOTHESIS); requestQueue.put(request); ensureThreadIsRunning(); return request.outBoolean.get(); } catch (ExecutionException error) { logger.atSevere().withCause(error).log( "finalizeCurrentHypothesis %s", EXECUTION_EXCEPTION_MESSAGE); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log( "finalizeCurrentHypothesis %s", INTERRUPTED_EXCEPTION_MESSAGE); } return false; } public void setCurrentHypothesis(TranscriptionResult resultSingleUtterance) { try { Request request = new Request(RequestType.SET_CURRENT_HYPOTHESIS); request.inTranscriptionResult = resultSingleUtterance; requestQueue.put(request); ensureThreadIsRunning(); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log( "setCurrentHypothesis %s", INTERRUPTED_EXCEPTION_MESSAGE); } } public Spanned getFormattedTranscript() { try { Request request = new Request(RequestType.GET_FORMATTED_TRANSCRIPT); requestQueue.put(request); ensureThreadIsRunning(); return request.outSpanned.get(); } catch (ExecutionException error) { logger.atSevere().withCause(error).log( "getFormattedTranscript %s", EXECUTION_EXCEPTION_MESSAGE); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log( "getFormattedTranscript %s", INTERRUPTED_EXCEPTION_MESSAGE); } return null; } public Spanned getMostRecentTranscriptSegment() { try { Request request = new Request(RequestType.GET_MOST_RECENT_TRANSCRIPT_SEGMENT); requestQueue.put(request); ensureThreadIsRunning(); return request.outSpanned.get(); } catch (ExecutionException error) { logger.atSevere().withCause(error).log( "getMostRecentTranscriptSegment %s", EXECUTION_EXCEPTION_MESSAGE); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log( "getMostRecentTranscriptSegment %s", INTERRUPTED_EXCEPTION_MESSAGE); } return null; } public Duration getTranscriptDuration() { try { Request request = new Request(RequestType.GET_TRANSCRIPT_DURATION); requestQueue.put(request); ensureThreadIsRunning(); return request.outDuration.get(); } catch (ExecutionException error) { logger.atSevere().withCause(error).log( "getTranscriptDuration %s", EXECUTION_EXCEPTION_MESSAGE); } catch (InterruptedException interrupted) { logger.atSevere().withCause(interrupted).log( "getTranscriptDuration %s", INTERRUPTED_EXCEPTION_MESSAGE); } return null; } /** * A service to be run on a separate thread that performs the formatting logic. The formatting * logic is controlled by the TranscriptionResultFormatter instance (which is not thread-safe). * Each job is sent to this class by adding a request into requestQueue. Jobs will be executed in * the order that they are placed in the queue. * *

If the queue remains empty for longer than 15 seconds, the run() method will complete and * the parent thread will end. However, it may be restarted in a new thread. */ private class TranscriptionResultFormatterService implements Runnable { private final TranscriptionResultFormatter impl; TranscriptionResultFormatterService() { impl = new TranscriptionResultFormatter(); } TranscriptionResultFormatterService(TranscriptionResultFormatterOptions options) { impl = new TranscriptionResultFormatter(options); } // Concurrency notes: As long as this task does not make callbacks into client code or // spawn additional tasks, there is no risk of it deadlocking. Be very careful in modifying it. @Override public void run() { Thread.currentThread().setName("SafeTranscriptionResultFormatterThread"); try { while (true) { // Try and pull from the queue. If the queue is empty for more than 15 seconds, complete // the thread. The parent class will start a new thread to process this service again if // another task arrives. Request request = requestQueue.poll(15, TimeUnit.SECONDS); if (request == null) { // We are ready to terminate the thread. synchronized (threadRestartLock) { // logger.atInfo().log("Formatter request queue is exhausted. %s", confinedThread); // Setting confinedThread to null is very important. This is how we signal in a // synchronizable way that this thread is no longer relevant. The parent object // can restart a new thread if more requests come in. confinedThread = null; } return; } switch (request.type) { case SET_OPTIONS: impl.setOptions(request.inOptions); break; case RESET: impl.reset(); break; case ADD_FINALIZED_RESULT: impl.addFinalizedResult(request.inTranscriptionResult); break; case CLEAR_CURRENT_HYPOTHESIS: impl.clearCurrentHypothesis(); break; case FINALIZE_CURRENT_HYPOTHESIS: request.outBoolean.set(impl.finalizeCurrentHypothesis()); break; case SET_CURRENT_HYPOTHESIS: impl.setCurrentHypothesis(request.inTranscriptionResult); break; case GET_FORMATTED_TRANSCRIPT: request.outSpanned.set(impl.getFormattedTranscript()); break; case GET_MOST_RECENT_TRANSCRIPT_SEGMENT: request.outSpanned.set(impl.getMostRecentTranscriptSegment()); break; case GET_TRANSCRIPT_DURATION: request.outDuration.set(impl.getTranscriptDuration()); break; } } } catch (InterruptedException interrupted) { // Interrupting causes the service to stop. logger.atSevere().withCause(interrupted).log("Formatter service has been interrupted"); } } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/SpeechDetectionPolicy.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.SampleProcessorInterface; /** * Decides whether audio should be passed to the recognizer. Unlike the SpeechDetector, this * is not trying to make a fine-grain estimate about whether there is speech or not, but instead * it decides how to manage sessions, possibly based on the output of a SpeechDetector. */ public interface SpeechDetectionPolicy extends SampleProcessorInterface { boolean shouldPassAudioToRecognizer(); void reset(); /** * Tells the detector that there is currently evidence of speech coming from a source that is * external to this class (for example, getting transcription results from an ASR engine). * *

Use of this function is certainly not required (implementations may ignore these cues by not * overriding this function), but it can be used to build speech detectors that consume less power * when there is external evidence of speech. */ default void cueEvidenceOfSpeech() {}; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/SpeechSession.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import com.google.audio.asr.SpeechRecognitionModelOptions; /** * Speech recognizers must use this interface. Note that any initialization that if *really* * expensive should happen in the factory, not the speech session, as the factory will setup once * before streaming occurs and persist across all sessions. */ public abstract class SpeechSession { private boolean initialized = false; private int sessionID; /** Returns true if internet is an initialization requirement. */ public abstract boolean requiresNetworkConnection(); /** * Any setup that requires network connection should happen here. * *

This must not be called multiple times. */ public void init( SpeechRecognitionModelOptions modelOptions, int bufferSizeSamples, int sessionID) { if (isInitialized()) { throw new IllegalStateException("Do not call initialize multiple times!"); } this.sessionID = sessionID; initImpl(modelOptions, bufferSizeSamples); initialized = true; } public int sessionID() { return sessionID; } protected abstract void initImpl( SpeechRecognitionModelOptions modelOptions, int bufferSizeSamples); /** Returns true if init has been called already. */ public final boolean isInitialized() { return initialized; } /** Passes audio to the session, formatted as int16 samples. */ public boolean processAudioBytes(byte[] buffer, int offset, int count) { if (!isInitialized()) { throw new IllegalStateException("Do not call processAudioBytes before init()!"); } return processAudioBytesImpl(buffer, offset, count); } protected abstract boolean processAudioBytesImpl(byte[] buffer, int offset, int count); /** * Begin the process of ending the speech session. The session need not be fully closed by the * time this function returns. To signal that the session is fully closed, use * SpeechSessionListener.onOkToTerminate() (the listener is passed into the session's * constructor). * *

This must not cause isInitialized to return false. This may be called multiple times. */ public void requestCloseSession() { if (!isInitialized()) { throw new IllegalStateException("Do not call requestCloseSession before init()!"); } requestCloseSessionImpl(); } protected abstract void requestCloseSessionImpl(); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/SpeechSessionFactory.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; /** Factory for creating online or offline speech sessions. */ public interface SpeechSessionFactory { SpeechSession create(SpeechSessionListener listener, int sampleRateHz); /** Cleans up any allocated resources, if there are any. */ default void cleanup() {} } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/SpeechSessionListener.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import com.google.audio.asr.TranscriptionResult; /** An interface for communicating recognition events to the RepeatingRecognitionSession. */ public interface SpeechSessionListener { /** * Tells the client that the recognizer has had an error from which we cannot recover. It is safe * to terminate the session. */ void onSessionFatalError(int sessionID, Throwable error); /** * Notifies that a new transcription result is available. If resultIsFinal is false, the results * are subject to change. */ void onResults(int sessionID, TranscriptionResult result, boolean resultIsFinal); /** Signals that no more audio should be sent to the recognizer. */ void onDoneListening(int sessionID); /** * Notifies that it is safe to kill the session. Called when the recognizer is done returning * results. */ void onOkToTerminateSession(int sessionID); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/TimeUtil.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import com.google.protobuf.Timestamp; import com.google.protobuf.util.Durations; import com.google.protobuf.util.Timestamps; import org.joda.time.Duration; import org.joda.time.Instant; /** * Conversions between the proto and Joda timestamp representations. * *

Note that toInstant() drops from nanosecond to millisecond precision (which shouldn't be * needed for ASR applications anyhow). */ public final class TimeUtil { public static Instant toInstant(Timestamp t) { return new Instant(Timestamps.toMillis(t)); } public static Timestamp toTimestamp(Instant t) { return Timestamps.fromMillis(t.getMillis()); } public static Duration convert(com.google.protobuf.Duration d) { return Duration.millis(Durations.toMillis(d)); } public static com.google.protobuf.Duration convert(Duration d) { return Durations.fromMillis(d.getMillis()); } private TimeUtil() {} } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/TranscriptionErrorPublisher.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; /** An interface for notifying the client about ASR errors. */ public interface TranscriptionErrorPublisher { /** Called when error happens. */ void onError(Throwable errorCause); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/TranscriptionResultFormatter.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import android.graphics.Color; import android.graphics.Typeface; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import com.google.audio.asr.TranscriptionResult; import com.google.audio.asr.TranscriptionResultFormatterOptions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.joda.time.Duration; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; /** * Creates a colored transcript in the format of {@link SpannedString} from {@link * TranscriptionResult} according to the configuration of {@link Options}. * *

This class is not thread-safe. If you intend to use this from multiple threads, consider * SafeTranscriptionResultFormatter. */ public class TranscriptionResultFormatter { private static final String WHITE = "#ffffffff"; // Alpha: 1 private static final String BLACK = "#de000000"; // Alpha: .87 // Color gradients can be generated using http://www.perbang.dk/rgbgradient/. // In order of ascending confidence. private static final ImmutableList LIGHT_THEME_COLORS = ImmutableList.of("#004ffa", "#1b55c8", "#375b96", "#526164", "#6e6732", "#8a6e00"); private static final ImmutableList DARK_THEME_COLORS = ImmutableList.of("#004ffa", "#306dc8", "#608c69", "#90aa64", "#c0c932", "#ffff00"); private static final ImmutableList SPEAKER_ID_COLORS = ImmutableList.of( "#4285f4", // blue "#ea4335", // red "#fbbc04", // yellow "#34a853", // green "#FA7B17", // orange "#F439A0", // pink "#A142F4", // purple "#24C1E0" // cyan ); private static final ImmutableList UPPER_CONFIDENCE_THRESHOLDS = ImmutableList.of(0.3, 0.55, 0.7, 0.8, 0.9, Double.POSITIVE_INFINITY); // The separator regex used to split a concatenated string of word values. private static final String JAPANESE_SPLITTER_REGEX = "\\|"; public static TranscriptionResultFormatterOptions noFormattingOptions() { return TranscriptionResultFormatterOptions.newBuilder() .setNumExtendedSilenceLineBreaks(0) .setNumLanguageSwitchLineBreaks(1) .setItalicizeCurrentHypothesis(false) .setTranscriptColoringStyle(TranscriptionResultFormatterOptions.TranscriptColoringStyle.NO_COLORING) .setTextColormap(TranscriptionResultFormatterOptions.TextColormap.DARK_THEME) .build(); } /** Formatted text and the TranscriptionResult that produced it. */ private static class CachedResult { public Spanned text; public TranscriptionResult result; public Spanned leadingWhitespace; CachedResult(TranscriptionResult result, Spanned text, Spanned leadingWhitespace) { this.result = result; this.text = text; this.leadingWhitespace = leadingWhitespace; } CharSequence getFormattedText() { return TextUtils.concat(leadingWhitespace, text); } } private TranscriptionResultFormatterOptions options; private Deque resultsDeque = new ArrayDeque<>(); private TranscriptionResult currentHypothesis; // A stored string of whitespace to add between extended silences. private String silenceLineBreak; // A stored string of whitespace to add between extended language switch. private String languageSwitchLineBreak; // A joda.org.Duration version of the options field of the same name. private Duration extendedSilenceDurationForLineBreaks; // The index of the last speaker contained in the most recently finalized result. -1 indicates // that no results have been seen. private int lastSpeakerId = -1; public TranscriptionResultFormatter() { setOptions(noFormattingOptions()); } public TranscriptionResultFormatter(TranscriptionResultFormatterOptions options) { setOptions(options); reset(); } /** * Sets the formatter options, which may include settings of current hypotheses in italics or * color transcripts by confidence. */ public void setOptions(TranscriptionResultFormatterOptions options) { this.options = options.toBuilder().build(); lastSpeakerId = -1; // Prepare the whitespace string. silenceLineBreak = createLineBreakString(options.getNumExtendedSilenceLineBreaks()); languageSwitchLineBreak = createLineBreakString(options.getNumLanguageSwitchLineBreaks()); extendedSilenceDurationForLineBreaks = TimeUtil.convert(options.getExtendedSilenceDurationForLineBreaks()); // Reformat the old list. Deque oldResultsDeque = resultsDeque; resultsDeque = new ArrayDeque<>(); for (CachedResult oldResult : oldResultsDeque) { addFinalizedResult(oldResult.result); } } /** * Creates the line break string. * * @param lineBreakCount line break count in the string. * @return the line break string according to the lineBreakCount. */ private String createLineBreakString(int lineBreakCount) { return Strings.repeat("\n", lineBreakCount); } /** Reset to initial state, before any calls to addFinalizedResult() or setCurrentHypothesis(). */ public void reset() { resultsDeque.clear(); lastSpeakerId = -1; clearCurrentHypothesis(); } /** * Commits a result to the final transcript. * *

NOTE: This does not clear the hypothesis. Users who get partial results (hypotheses) should * prefer calling setCurrentHypothesis(...) and then finalizeCurrentHypothesis(). */ public void addFinalizedResult(TranscriptionResult resultSingleUtterance) { String lineBreak = obtainLineBreaksFromLastFinalizedResult(resultSingleUtterance); resultsDeque.add( new CachedResult( resultSingleUtterance.toBuilder().build(), formatSingleFinalized(resultSingleUtterance, !lineBreak.isEmpty()), SpannedString.valueOf(lineBreak))); lastSpeakerId = getLastSpeakerIdTag(resultSingleUtterance); } /** * Removes the current hypothesis so that only the finalized results will be in the transcript. */ public void clearCurrentHypothesis() { currentHypothesis = null; } /** * Commits the currently stored hypothesis to the finalized text buffer and clears the hypothesis. * * @return true if it has results to finalize, otherwise false. */ public boolean finalizeCurrentHypothesis() { if (currentHypothesis == null) { return false; } addFinalizedResult(currentHypothesis); clearCurrentHypothesis(); return true; } /** * Sets the estimate of the current text, this result is expected to change. Once it is done * changing, commit it, by passing it to addFinalizedResult(). */ public void setCurrentHypothesis(TranscriptionResult resultSingleUtterance) { currentHypothesis = resultSingleUtterance.toBuilder().build(); } /** Returns the current finalized text with the hypothesis appended to the end. */ public Spanned getFormattedTranscript() { SpannableStringBuilder builder = new SpannableStringBuilder(); for (CachedResult timestampedAndCachedResult : resultsDeque) { builder.append(timestampedAndCachedResult.getFormattedText()); } builder.append(getFormattedHypothesis()); return new SpannedString(builder); } /** Returns the latest sentence from transcription result. */ public Spanned getMostRecentTranscriptSegment() { SpannableStringBuilder builder = new SpannableStringBuilder(); builder.append(getFormattedHypothesis()); if (!TextUtils.isEmpty(builder)) { return new SpannedString(builder); } if (!resultsDeque.isEmpty()) { CachedResult timestampedAndCachedResult = resultsDeque.getLast(); builder.append(timestampedAndCachedResult.getFormattedText()); } return new SpannedString(builder); } /** Get the transcription's duration time. */ public Duration getTranscriptDuration() { if (resultsDeque.isEmpty()) { return Duration.ZERO; } return new Duration( TimeUtil.toInstant(resultsDeque.peekFirst().result.getStartTimestamp()), TimeUtil.toInstant(resultsDeque.peekLast().result.getEndTimestamp())); } private Spannable getFormattedHypothesis() { if (currentHypothesis == null) { return new SpannableString(""); } SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); String lineBreak = obtainLineBreaksFromLastFinalizedResult(currentHypothesis); boolean precededByLineBreak = !lineBreak.isEmpty(); if (precededByLineBreak) { spannableStringBuilder.append(lineBreak); } spannableStringBuilder.append(formatHypothesis(currentHypothesis, precededByLineBreak)); return SpannableString.valueOf(spannableStringBuilder); } private Spannable formatHypothesis(TranscriptionResult result, boolean precededByLineBreak) { Spannable spannable = formatSingleFinalized(result, precededByLineBreak); if (options.getItalicizeCurrentHypothesis()) { spannable.setSpan( new StyleSpan(Typeface.ITALIC), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return spannable; } /** A function that maps a Word to a six digit hex color (e.g. #a0b341). */ private interface ColorByWordFunction { String getColor(TranscriptionResult.Word w); } /** * Format a single result. precededByLineBreak is used to determine if a speaker indicator should * be added to reestablish context after a newline. */ private Spannable formatSingleFinalized( TranscriptionResult result, boolean precededByLineBreak) { // Trim leading spaces, but ensure that there will be a space before the next word. String normalizedTranscript = result.getText().trim() + " "; if (result.getWordLevelDetailList().isEmpty()) { // Process the transcript as a whole. String color = ""; switch (options.getTranscriptColoringStyle()) { case COLOR_BY_SPEAKER_ID: color = getColorFromSpeakerId(result.getSpeakerInfo().getSpeakerId()); break; case COLOR_BY_UTTERANCE_LEVEL_CONFIDENCE: color = getColorFromConfidence(result); break; case COLOR_BY_WORD_LEVEL_CONFIDENCE: case NO_COLORING: case UNSPECIFIED_COLORING_STYLE: color = getDefaultColorFromTheme(); break; } if (options.getSpeakerIndicationStyle() == TranscriptionResultFormatterOptions.SpeakerIndicationStyle.SHOW_SPEAKER_NUMBER && (precededByLineBreak || result.getSpeakerInfo().getSpeakerId() != lastSpeakerId)) { boolean requiresLineBreak = lastSpeakerId != -1 && !precededByLineBreak; normalizedTranscript = newSpeakerChevron(result.getSpeakerInfo().getSpeakerId(), requiresLineBreak) + normalizedTranscript; } // Make sure the utterance ends in a trailing space so that words don't get merged together. return makeColoredString(normalizedTranscript, color); } else { // Process each word of the transcript separately. ColorByWordFunction colorFunction = w -> getDefaultColorFromTheme(); switch (options.getTranscriptColoringStyle()) { case COLOR_BY_WORD_LEVEL_CONFIDENCE: colorFunction = word -> getColorFromConfidence(word.getConfidence()); break; case COLOR_BY_UTTERANCE_LEVEL_CONFIDENCE: colorFunction = word -> getColorFromConfidence(result); // Word-independent. break; case COLOR_BY_SPEAKER_ID: colorFunction = word -> getColorFromSpeakerId(word.getSpeakerInfo().getSpeakerId()); break; case NO_COLORING: case UNSPECIFIED_COLORING_STYLE: colorFunction = word -> getDefaultColorFromTheme(); break; } return addPerWordColoredStringToResult( normalizedTranscript, result.getLanguageCode(), result.getWordLevelDetailList(), precededByLineBreak, colorFunction); } } /** * Obtains line breaks between the last finalized result and current result. It would return an * empty string if no finalized transcript result existed. (Current result is he first element.) */ private String obtainLineBreaksFromLastFinalizedResult(TranscriptionResult current) { return resultsDeque.isEmpty() ? "" : obtainLineBreaksBetweenTwoResults(resultsDeque.getLast(), current); } private String obtainLineBreaksBetweenTwoResults( CachedResult previous, TranscriptionResult current) { boolean languageSwitched = !previous.result.getLanguageCode().equals(current.getLanguageCode()); if (options.getNumExtendedSilenceLineBreaks() > 0) { // Previous element is not whitespace. Duration timestampDifference = new Duration( TimeUtil.toInstant(previous.result.getEndTimestamp()), TimeUtil.toInstant(current.getStartTimestamp())); if (timestampDifference.isLongerThan(extendedSilenceDurationForLineBreaks)) { // If language switch and silence both happened, return the longer line break. return languageSwitched ? getLineBreaksWhenSilenceAndLanguageSwitch() : silenceLineBreak; } } return languageSwitched ? languageSwitchLineBreak : ""; } /** Returns the String contains more new line breaks between language switch and silence. */ private String getLineBreaksWhenSilenceAndLanguageSwitch() { if (options.getNumExtendedSilenceLineBreaks() >= options.getNumLanguageSwitchLineBreaks()) { return silenceLineBreak; } return languageSwitchLineBreak; } private static String getLanguageWithoutDialect(String languageCode) { if (TextUtils.isEmpty(languageCode)) { return ""; } return languageCode.split("-", -1)[0]; } /** * Returns string with Hiragana only if language is Japanese. Otherwise, returned string is with * any leading and trailing whitespace removed. */ private static String formatWord(String languageCode, String word) { String language = getLanguageWithoutDialect(languageCode); if ("ja".equalsIgnoreCase(language)) { // Japanese ASR results could contain two parts per word, the former would be one of // Hiragana, Katakana, or Kanji, and the latter would be Katakana or none. Here extract // the former. return word.split(JAPANESE_SPLITTER_REGEX, -1)[0]; } return word.trim(); } /** * If the word occurs as a substring within the rawTranscript, then the substring starting from * the last occurrence of the word and extends to the end is added to intermediateBuilder. We * assume the transcript is formatted perfectly, and then we don't worry about the word divider * between words for all languages if we construct the transcript by words level detail. */ private static boolean checkWordExistedThenAdd( StringBuilder rawTranscript, StringBuilder intermediateBuilder, String word) { int index = rawTranscript.lastIndexOf(word); if (index == -1) { return false; } String transcriptToTheEnd = rawTranscript.substring(index); intermediateBuilder.insert(0, transcriptToTheEnd); rawTranscript.delete(index, rawTranscript.length()); return true; } /** * Generates a Spannable with text formatted at the word level. * * @param wholeStringTranscript the whole transcript, formatted to have no leading spaces and a * single trailing space * @param languageCode string language code, for example "en-us" or "ja" * @param words the list of words contained in wholeStringTranscript * @param colorFunction maps a word to a hex color */ private Spannable addPerWordColoredStringToResult( String wholeStringTranscript, String languageCode, List words, boolean precededByLineBreak, ColorByWordFunction colorFunction) { StringBuilder rawTranscript = new StringBuilder(wholeStringTranscript); boolean wordFound = false; String color = ""; SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); StringBuilder intermediateBuilder = new StringBuilder(); // Group adjacent words of the same color within the same span tag. // Traverse in reverse then a space divider will be at the end of word. List reverseWords = Lists.reverse(words); for (int wordIndex = 0; wordIndex < reverseWords.size(); ++wordIndex) { TranscriptionResult.Word word = reverseWords.get(wordIndex); String nextColor = colorFunction.getColor(word); if (wordFound) { if (!color.equals(nextColor)) { spannableStringBuilder.insert( 0, makeColoredString(intermediateBuilder.toString(), color)); intermediateBuilder = new StringBuilder(); wordFound = false; } if (options.getSpeakerIndicationStyle() == TranscriptionResultFormatterOptions.SpeakerIndicationStyle.SHOW_SPEAKER_NUMBER) { // If the speaker has changed or if the text was preceded by a space, add a chevron. int previousSpeaker = reverseWords.get(wordIndex - 1).getSpeakerInfo().getSpeakerId(); if (word.getSpeakerInfo().getSpeakerId() != previousSpeaker) { boolean needsAdditionalNewline = previousSpeaker != -1 && !precededByLineBreak; intermediateBuilder.insert( 0, newSpeakerChevron( reverseWords.get(wordIndex - 1).getSpeakerInfo().getSpeakerId(), needsAdditionalNewline)); spannableStringBuilder.insert( 0, makeColoredString(intermediateBuilder.toString(), color)); intermediateBuilder = new StringBuilder(); wordFound = false; } } } // We'll try to find previous word if we can't find current word in the rawTranscript. // Append the string started from the word to the end if found. wordFound |= checkWordExistedThenAdd( rawTranscript, intermediateBuilder, formatWord(languageCode, word.getText())); color = nextColor; } boolean forceChevron = precededByLineBreak || words.get(0).getSpeakerInfo().getSpeakerId() != lastSpeakerId; intermediateBuilder.insert(0, rawTranscript.toString()); if (options.getSpeakerIndicationStyle() == TranscriptionResultFormatterOptions.SpeakerIndicationStyle.SHOW_SPEAKER_NUMBER && intermediateBuilder.length() != 0 && forceChevron) { intermediateBuilder.insert( 0, newSpeakerChevron( words.get(0).getSpeakerInfo().getSpeakerId(), lastSpeakerId != -1 && !precededByLineBreak)); } spannableStringBuilder.insert(0, makeColoredString(intermediateBuilder.toString(), color)); return SpannableString.valueOf(spannableStringBuilder); } /** * Generates a {@link SpannableString} containing a colored string. * * @param message a string to append to cachedFinalizedResult * @param color a six-character hex string beginning with a pound sign */ private SpannableString makeColoredString(String message, String color) { int textColor = Color.parseColor(color); SpannableString spannableString = new SpannableString(message); spannableString.setSpan( new ForegroundColorSpan(textColor), 0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return spannableString; } /** * Get a string hex color associated with a confidence value on the range [0, 1] according to the * confidence in {@link TranscriptionResult}. */ private String getColorFromConfidence(TranscriptionResult result) { if (result.hasConfidence()) { return getColorFromConfidence(result.getConfidence()); } return getDefaultColorFromTheme(); } /** * Get a string hex color associated with a confidence value on the range [0, 1] according to * specified confidence. */ private String getColorFromConfidence(float confidence) { ImmutableList colormap = getColorList(options.getTextColormap()); for (int i = 0; i < UPPER_CONFIDENCE_THRESHOLDS.size(); ++i) { if (confidence <= UPPER_CONFIDENCE_THRESHOLDS.get(i)) { return colormap.get(i); } } // Won't happen because upper bound of UPPER_CONFIDENCE_THRESHOLDS is infinity. return getDefaultColorFromTheme(); } /** Returns the hex code of the default text color according to the theme. */ private String getDefaultColorFromTheme() { switch (options.getTextColormap()) { case DARK_THEME: return WHITE; case LIGHT_THEME: case UNSPECIFIED_THEME: return BLACK; } return WHITE; } /** * Get a string hex color associated with the speaker number. Currently this supports up to 4 * speakers. */ private String getColorFromSpeakerId(int speakerID) { return SPEAKER_ID_COLORS.get(speakerID % SPEAKER_ID_COLORS.size()); } private static ImmutableList getColorList(TranscriptionResultFormatterOptions.TextColormap colormap) { switch (colormap) { case LIGHT_THEME: case UNSPECIFIED_THEME: return LIGHT_THEME_COLORS; case DARK_THEME: return DARK_THEME_COLORS; } return DARK_THEME_COLORS; } private static String newSpeakerChevron(int tag, boolean includesNewline) { return (includesNewline ? "\n≫ " : "≫ ") + Integer.toString(tag) + ": "; } private static int getLastSpeakerIdTag(TranscriptionResult result) { if (result.getWordLevelDetailCount() == 0) { return result.getSpeakerInfo().getSpeakerId(); } else { return result .getWordLevelDetailList() .get(result.getWordLevelDetailCount() - 1) .getSpeakerInfo() .getSpeakerId(); } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/TranscriptionResultReceiver.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import com.google.audio.asr.TranscriptionResult; import com.google.common.base.Objects; import com.google.common.flogger.FluentLogger; import java.lang.ref.WeakReference; /** * Handles results as they come in from the recognition module and posts them back to the * RepeatingRecognitionSession. */ class TranscriptionResultReceiver implements SpeechSessionListener { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final WeakReference postHandlerRef; public TranscriptionResultReceiver(RepeatingRecognitionSession.PostHandler postHandler) { this.postHandlerRef = new WeakReference<>(postHandler); } @Override public void onSessionFatalError(int sessionID, Throwable error) { logger.atSevere().withCause(error).log("Session #%d ended fatally.", sessionID); post( RequestForRecognitionThread.newBuilder() .setAction( errorIndicatesLossOfConnection(error) ? RequestForRecognitionThread.Action.HANDLE_NETWORK_CONNECTION_FATAL_ERROR : RequestForRecognitionThread.Action.HANDLE_NON_NETWORK_CONNECTION_FATAL_ERROR) .setSessionID(sessionID) .setErrorCause(error) .build()); } @Override public void onResults(int sessionID, TranscriptionResult result, boolean resultsAreFinal) { post( RequestForRecognitionThread.newBuilder() .setSessionID(sessionID) .setAction(RequestForRecognitionThread.Action.POST_RESULTS) .setResult(result, resultsAreFinal) .build()); } @Override public void onDoneListening(int sessionID) { logger.atInfo().log("Session #%d scheduled to be ended gracefully.", sessionID); post(sessionID, RequestForRecognitionThread.Action.REQUEST_TO_END_SESSION); } @Override public void onOkToTerminateSession(int sessionID) { logger.atInfo().log("Session #%d scheduled to be terminated.", sessionID); post(sessionID, RequestForRecognitionThread.Action.OK_TO_TERMINATE_SESSION); } private boolean errorIndicatesLossOfConnection(Throwable error) { boolean isGrpcError = error instanceof io.grpc.StatusRuntimeException; if (isGrpcError) { return Objects.equal(io.grpc.Status.fromThrowable(error), io.grpc.Status.UNAVAILABLE); } return false; } private void post(int sessionID, RequestForRecognitionThread.Action request) { post( RequestForRecognitionThread.newBuilder() .setAction(request) .setSessionID(sessionID) .build()); } private void post(RequestForRecognitionThread request) { RepeatingRecognitionSession.PostHandler postHandler = postHandlerRef.get(); if (postHandler == null) { return; } postHandler.post(request); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/TranscriptionResultUpdatePublisher.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr; import android.text.Spanned; /** An interface for notifying the most recent transcription comes from the recognizer. */ public interface TranscriptionResultUpdatePublisher { /** A notification about the nature of the update. */ enum UpdateType { TRANSCRIPT_UPDATED, TRANSCRIPT_FINALIZED, TRANSCRIPT_CLEARED, } /** Enum defining kinds of transcript result the listeners expect to handle. */ enum ResultSource { /** Provides the most recent transcript result in current session. */ MOST_RECENT_SEGMENT, /** Provides the whole transcript result in current session. */ WHOLE_RESULT } /** * Called when transcription updates from the server. * * @param formattedResult The formatted result for the transcription. * @param updateType The nature of the update. */ void onTranscriptionUpdate(Spanned formattedResult, UpdateType updateType); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/asrhelpers/CircularByteBuffer.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers; import com.google.common.flogger.FluentLogger; import java.util.ArrayList; /** * A storage unit for reading chunks of bytes at a time. Only a single writer may populate * data in the CircularByteBuffer, but multiple asynchronous reads are supported as long as the * write pointer doesn't overlap with any of the read pointers. If this fails to be true, the output * bytes may be corrupted. */ public class CircularByteBuffer { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final byte[] buffer; private final int capacity; private int writeHead = 0; private int cumulativeWritten = 0; private final ArrayList allReaders = new ArrayList(); /** Reads from a CircularBuffer by maintaining its own position. */ public static class Reader { private final CircularByteBuffer owner; private int readHead = 0; private long cumulativeRead = 0; // Can be used to see if samples were lost prior to most recent read. // If so many samples have been written that the write pointer overlaps one of the // read pointers, samples will be dropped. This is logged in read(), but you can also, // as the caller of read(), check whether samples were lost by checking to see if // droppedSamplesWarning is true. For example, the following lines would fail an assertion if // data were lost: // reader.read(some_buffer); // assert reader.droppedSamplesWarning; // This value is set, but never read, by the CircularByteBuffer or its Readers. public boolean droppedSamplesWarning = false; /** * Read, updating the read pointer. numBytes must be less than buffer capacity. * @return true on success. */ public boolean read(byte[] dst, int offset, int numBytes) { if (owner.read(this, dst, offset, numBytes)) { cumulativeRead += numBytes; return true; } return false; } public boolean read(byte[] dst) { return read(dst, 0, dst.length); } /** * Read, without updating the read pointer. numBytes must be less than buffer capacity. * @return true on success. */ public boolean peek(byte[] dst, int offset, int numBytes) { return owner.peek(this, dst, offset, numBytes); } public boolean peek(byte[] dst) { return peek(dst, 0, dst.length); } public void advance(int advanceBy) { readHead = (readHead + advanceBy) % owner.getCapacity(); } public int availableBytes() { return Math.min(owner.getCapacity(), (int) (owner.getCumulativeWritten() - cumulativeRead)); } private Reader(CircularByteBuffer owner) { this.owner = owner; } private void reset() { readHead = 0; cumulativeRead = 0; } } public CircularByteBuffer(int capacity) { buffer = new byte[capacity]; this.capacity = capacity; } /** * Get a reader for the circular buffer. You may use several of these independently. * You should call this before the first call to write() occurring after construction or * calling reset(). Otherwise, you may start reading from somewhere in the middle of the stream. */ public Reader newReader() { synchronized (allReaders) { Reader reader = new Reader(this); allReaders.add(reader); return reader; } } /** Copy data from src into the circular buffer. Returns false when bytesToWrite * exceeds capacity. */ public synchronized boolean write(byte[] src, int offset, int bytesToWrite) { if (bytesToWrite > capacity) { return false; } if (bytesToWrite == 0) { return true; } if (writeHead + bytesToWrite <= capacity) { System.arraycopy(src, offset, buffer, writeHead, bytesToWrite); } else { // Data wraps around buffer edge. int entriesBeforeWrap = capacity - writeHead; System.arraycopy(src, offset, buffer, writeHead, entriesBeforeWrap); System.arraycopy( src, offset + entriesBeforeWrap, buffer, 0, bytesToWrite - entriesBeforeWrap); } writeHead = (writeHead + bytesToWrite) % capacity; cumulativeWritten += bytesToWrite; return true; } public boolean write(byte[] src) { return write(src, 0, src.length); } public int getCapacity() { return capacity; } /** * Resets the circular buffer and all of the readers that have been issued. The class is reset to * its initial state upon construction. Readers that have been issued do not get removed. */ public synchronized void reset() { java.util.Arrays.fill(buffer, (byte) 0); writeHead = 0; cumulativeWritten = 0; synchronized (allReaders) { for (Reader reader : allReaders) { reader.reset(); } } } /** * Read data from the buffer and update the read pointer. If the reader is far * enough behind (the write pointer passes the read pointer), samples will be dropped to catch up. */ private boolean read(Reader reader, byte[] dst, int offset, int numBytes) { reader.droppedSamplesWarning = false; if (cumulativeWritten - reader.cumulativeRead > capacity) { logger.atSevere().log("We lost data before this read!"); // Skip ahead to the very end of the buffer. int skipAmount = (int) (cumulativeWritten - reader.cumulativeRead) - numBytes; reader.advance(skipAmount); reader.cumulativeRead += skipAmount; reader.droppedSamplesWarning = true; } if (peek(reader, dst, offset, numBytes)) { reader.advance(numBytes); return true; } return false; } /** * Read data from the buffer without modifying the reader. Note that if the * reader is far enough behind, this will return corrupted data. */ private boolean peek(Reader reader, byte[] dst, int offset, int numBytes) { int bytesToRead = numBytes; if (bytesToRead == 0) { return true; } if (bytesToRead < 0 || bytesToRead > reader.availableBytes()) { return false; } int endOfReadSection = (reader.readHead + bytesToRead) % capacity; if (reader.readHead < endOfReadSection) { System.arraycopy(buffer, reader.readHead, dst, offset, endOfReadSection - reader.readHead); } else { // Data wraps around buffer edge. System.arraycopy(buffer, reader.readHead, dst, offset, capacity - reader.readHead); int entriesBeforeWrap = capacity - reader.readHead; System.arraycopy( buffer, 0, dst, offset + capacity - reader.readHead, bytesToRead - entriesBeforeWrap); } return true; } public synchronized long getCumulativeWritten() { return cumulativeWritten; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/asrhelpers/NetworkConnectionChecker.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.os.Build.VERSION_CODES.N; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkInfo; import android.os.Build.VERSION; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; import com.google.audio.NetworkState; import com.google.common.base.Preconditions; import com.google.common.flogger.FluentLogger; /** * Checks whether or not there is currently a connection and if that connection is Wifi. Need to * call {@link #unregisterNetworkCallback()} before it is destroyed. */ public class NetworkConnectionChecker { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final ConnectivityManager connectionManager; private final NetworkCallback networkCallback; private final MutableLiveData state; private final Context context; private final BroadcastReceiver networkStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { synchronized (state) { state.postValue(getNetworkState()); } } }; public NetworkConnectionChecker(Context context) { Preconditions.checkNotNull( context, "You need to pass a context to the NetworkConnectionChecker"); this.context = context; this.connectionManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); this.networkCallback = new NetworkCallback() { @Override public void onAvailable(Network network) { synchronized (state) { logger.atConfig().log("Network is available."); state.postValue( NetworkState.newBuilder() .setConnected(true) .setNetworkMetered(connectionManager.isActiveNetworkMetered()) .build()); } } @Override public void onLost(Network network) { synchronized (state) { logger.atConfig().log("Network is unavailable."); state.postValue( NetworkState.newBuilder() .setConnected(false) .setNetworkMetered(connectionManager.isActiveNetworkMetered()) .build()); } } }; state = new MutableLiveData<>(); registerNetworkCallback(); } public void addNetworkStateObserver(LifecycleOwner owner, Observer observer) { synchronized (state) { state.observe(owner, observer); } } protected NetworkState getNetworkState() { NetworkInfo activeNetwork = connectionManager.getActiveNetworkInfo(); boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); NetworkState state = NetworkState.newBuilder() .setConnected(isConnected) .setNetworkMetered(connectionManager.isActiveNetworkMetered()) .build(); return state; } public boolean isConnected() { synchronized (state) { return state.getValue().getConnected(); } } /** * Applications can skip register if they don't need register/unregister many times. Callback * register is done in the constructor. */ public void registerNetworkCallback() { synchronized (state) { state.postValue(getNetworkState()); } if (VERSION.SDK_INT >= N) { connectionManager.registerDefaultNetworkCallback(networkCallback); } else { context.registerReceiver(networkStateReceiver, new IntentFilter(CONNECTIVITY_ACTION)); } } /** Note this must be called if NetworkConnectionChecker is not being used anymore. */ public void unregisterNetworkCallback() { try { if (VERSION.SDK_INT >= N) { connectionManager.unregisterNetworkCallback(networkCallback); } else { context.unregisterReceiver(networkStateReceiver); } } catch (IllegalArgumentException unregisteredCallbackException) { logger.atWarning().log("Tried to unregister network callback already unregistered."); } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/asrhelpers/ResponseTextUiAdapter.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.teamopensmartglasses.smartglassesmanager.R; import java.util.List; public class ResponseTextUiAdapter extends RecyclerView.Adapter { private List data; public ResponseTextUiAdapter(List data) { this.data = data; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.response_text_box, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { String text = data.get(position); holder.textView.setText(text); } @Override public int getItemCount() { return data.size(); } static class ViewHolder extends RecyclerView.ViewHolder { TextView textView; ViewHolder(@NonNull View itemView) { super(itemView); textView = itemView.findViewById(R.id.textView); } } public void addText(String text) { data.add(text); notifyItemInserted(data.size() - 1); } public void clearTexts() { data.clear(); notifyDataSetChanged(); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/asrhelpers/SampleProcessorInterface.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers; /** An interface for sending samples to an object. */ public interface SampleProcessorInterface { public void init(int blockSizeSamples); /** * Samples are PCM, 16-bit samples, formatted as a byte stream. */ public void processAudioBytes(byte[] bytes, int offset, int length); public default void processAudioBytes(byte[] bytes) { processAudioBytes(bytes, 0, bytes.length); } /** * Call when you want the interface to stop playing. Playing may restart, so don't * deallocate resources here. */ public void stop(); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/asrhelpers/SpeakerIDLabeler.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers; import com.google.audio.SpeakerIdInfo; import org.joda.time.Instant; /** * An interface for classes that estimates labels for individual speakers. */ public interface SpeakerIDLabeler extends SampleProcessorInterface { /** * Tells the diarizer what time it is *now*. The expectation is that time will be incremented * within the calls to processAudioBytes based on the number of samples that are passed. */ void setReferenceTimestamp(Instant now); /** * Asks the diarizer which speaker was most likely to be active during the time interval (start, * end). The same request may be made several times for the same interval, so this function should * be very inexpensive. */ SpeakerIdInfo getSpeakerIDForTimeInterval(Instant start, Instant end); /** * Clears the labels currently stored in the diarizer. It is useful to periodically clear the * labels (such as at the start of every new utterance) in order to keep small the data structure * that holds the diarization timestamps. */ void clearSpeakerIDTimestamps(); /** Resets the state of the diarizer as if no audio has been seen. */ void reset(); } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/asr/asrhelpers/StreamingAudioEncoder.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers; import static com.google.common.base.Preconditions.checkArgument; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import com.google.audio.CodecAndBitrate; import com.google.common.collect.ImmutableList; import com.google.common.flogger.FluentLogger; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; /** * Converts from uncompressed 16-bit PCM data to encoded data. * *

You may call the sequence (init, processAudioBytes, ..., processAudioBytes, flush, stop) * multiple times. * *

Note that AMR-WB encoding is mandatory for handheld devices and OggOpus is supported * regardless of device. */ // Based on examples from https://developer.android.com/reference/android/media/MediaCodec // and some reference tests: // https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/EncoderTest.java public class StreamingAudioEncoder { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final int BYTES_PER_SAMPLE = 2; // This is not meaningful for OggOpus, which does not rely on the AndroidSystemEncoder. private boolean useDeprecatedEncoder = false; /** State variables for basic control flow management. */ private boolean flushed; private boolean initialized = false; /** An exception for anything that goes wrong with the coder as a result of misuse. */ public static class EncoderException extends Exception { public EncoderException(String message) { super(message); } } /** Describes the general class of codecs. */ public enum CodecType { UNSPECIFIED, AMRWB, FLAC, OGG_OPUS, } CodecType codecType = CodecType.UNSPECIFIED; private StreamingAudioInternalEncoder impl; /** Creates an audio encoder. */ public StreamingAudioEncoder(boolean useDeprecatedEncoder) { this.useDeprecatedEncoder = useDeprecatedEncoder; } public StreamingAudioEncoder() { this(VERSION.SDK_INT <= VERSION_CODES.KITKAT_WATCH); } private interface StreamingAudioInternalEncoder { void init(int sampleRateHz, CodecAndBitrate codecAndBitrate, boolean useVbr) throws EncoderException, IOException; byte[] processAudioBytes(byte[] input, int offset, int length); byte[] flushAndStop(); } /** * Prepares a codec to stream. This may be called only if instance is uninitialized (prior to a * call to init() or after a call to stop()). * * @throws IOException if codec cannot be created. * @throws EncoderException if sample rate is not 16kHz or if no suitable encoder exists on device * for the requested format. */ public void init(int sampleRateHz, CodecAndBitrate codecAndBitrate, boolean allowVbr) throws EncoderException, IOException { codecType = lookupCodecType(codecAndBitrate); if (codecType == CodecType.OGG_OPUS) { impl = new OggOpusEncoder(); } else { impl = new AndroidSystemEncoder(useDeprecatedEncoder); } impl.init(sampleRateHz, codecAndBitrate, allowVbr); initialized = true; flushed = false; } /** * Encodes 16-bit PCM audio. This will not always return bytes and will block until the codec has * no output to offer. Must be called after init(). * * @param input array of audio samples formatted as raw bytes (i.e., two bytes per sample). buffer * may be of any size. * @param offset the offset of the first byte to process * @param length the number of bytes to process from input * @return bytes of compressed audio */ public byte[] processAudioBytes(byte[] input, int offset, int length) { if (!initialized) { throw new IllegalStateException("You forgot to call init()!"); } if (flushed) { throw new IllegalStateException("Cannot process more bytes after flushing."); } return impl.processAudioBytes(input, offset, length); } public byte[] processAudioBytes(byte[] input) { return processAudioBytes(input, 0, input.length); } /** Stop the codec. Call init() before using again. */ public byte[] flushAndStop() { if (!initialized) { throw new IllegalStateException("You forgot to call init()!"); } if (flushed) { throw new IllegalStateException("Already flushed. You must reinitialize."); } flushed = true; byte[] flushedBytes = impl.flushAndStop(); initialized = false; codecType = CodecType.UNSPECIFIED; return flushedBytes; } /** * Can be used to test if codec will work or not on a given device. This will always return the * same value no matter when you call it. */ public static boolean isEncoderSupported(CodecAndBitrate encoderInfo) { CodecType type = lookupCodecType(encoderInfo); if (type == CodecType.OGG_OPUS) { // We support Opus directly via the OggOpusEncoder class. return true; } return searchAmongAndroidSupportedCodecs(getMime(type)) != null; } public CodecType getCodecType() { return codecType; } private static String getMime(CodecType codecAndBitrate) { // MediaFormat.MIMETYPE_AUDIO_AMR_WB requires SDK >= 21. switch (codecAndBitrate) { case AMRWB: return "audio/amr-wb"; case FLAC: return "audio/flac"; case OGG_OPUS: // Not supported by android system, so we don't need a MIME. case UNSPECIFIED: return ""; } return ""; } private static CodecType lookupCodecType(CodecAndBitrate codecAndBitrate) { switch (codecAndBitrate) { case AMRWB_BITRATE_6KBPS: case AMRWB_BITRATE_8KBPS: case AMRWB_BITRATE_12KBPS: case AMRWB_BITRATE_14KBPS: case AMRWB_BITRATE_15KBPS: case AMRWB_BITRATE_18KBPS: case AMRWB_BITRATE_19KBPS: case AMRWB_BITRATE_23KBPS: case AMRWB_BITRATE_24KBPS: return CodecType.AMRWB; case FLAC: return CodecType.FLAC; case OGG_OPUS_BITRATE_12KBPS: case OGG_OPUS_BITRATE_16KBPS: case OGG_OPUS_BITRATE_24KBPS: case OGG_OPUS_BITRATE_32KBPS: case OGG_OPUS_BITRATE_64KBPS: case OGG_OPUS_BITRATE_96KBPS: case OGG_OPUS_BITRATE_128KBPS: return CodecType.OGG_OPUS; case UNDEFINED: return CodecType.UNSPECIFIED; } return CodecType.UNSPECIFIED; } /** * Searches for a codec that implements the requested format conversion. Android framework encoder * only. */ private static MediaCodecInfo searchAmongAndroidSupportedCodecs(String mimeType) { int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecAndBitrate = MediaCodecList.getCodecInfoAt(i); if (!codecAndBitrate.isEncoder()) { continue; } String[] codecTypes = codecAndBitrate.getSupportedTypes(); for (int j = 0; j < codecTypes.length; j++) { if (codecTypes[j].equalsIgnoreCase(mimeType)) { return codecAndBitrate; } } } return null; } /** An encoder that relies on the Android framework's multimedia encoder. */ private static class AndroidSystemEncoder implements StreamingAudioInternalEncoder { // If we can't supply a buffer immediately, we wait until the next one, which is timed at the // microphone & block rate of the audio supplier. Waiting less than that time and getting // samples // before the next input buffer would reduce latency. private static final long WAIT_TIME_MICROSECONDS = 1000; // Joda doesn't support microseconds. /** * Notes when the codec formatting change has occurred. This should happen only once at the * start of streaming. Otherwise, there is an error. */ private boolean formatChangeReportedOnce; private MediaCodec codec; private boolean useDeprecatedEncoder = false; private CodecType codecType; private int sampleRateHz; /** Prevents trying to flush multiple times. */ private boolean successfullyFlushed; /** Keeps track of whether the header was injected into the stream. */ private boolean addedHeader; /** * The number of samples that are passed to the underlying codec at once. It's not clear that * one value for this will work better than any other, but powers of two are usually fast, and a * larger CHUNK_SIZE_SAMPLES both reduces the number of buffers we have to wait for and doesn't * prevent sending smaller blocks of samples. */ private static final int CHUNK_SIZE_SAMPLES = 2048; private static final int CHUNK_SIZE_BYTES = BYTES_PER_SAMPLE * CHUNK_SIZE_SAMPLES; // Used only on very old SDKs (pre VERSION_CODES.KITKAT_WATCH). private ByteBuffer[] inputBuffersPreKitKat; private ByteBuffer[] outputBuffersPreKitKat; /** Creates an audio encoder. */ public AndroidSystemEncoder(boolean useDeprecatedEncoder) { this.useDeprecatedEncoder = useDeprecatedEncoder; this.codecType = CodecType.UNSPECIFIED; } // Note that VBR is not currently supported for the AndroidStreamingEncoder. @Override public void init(int sampleRateHz, CodecAndBitrate codecAndBitrate, boolean allowVbr) throws EncoderException, IOException { codecType = lookupCodecType(codecAndBitrate); if (codecType == CodecType.UNSPECIFIED || codecType == CodecType.OGG_OPUS) { throw new EncoderException("Codec not set properly."); } if (codecType == CodecType.AMRWB && sampleRateHz != 16000) { throw new EncoderException("AMR-WB encoder requires a sample rate of 16kHz."); } MediaCodecInfo codecInfo = searchAmongAndroidSupportedCodecs(getMime(codecType)); if (codecInfo == null) { throw new EncoderException("Encoder not found."); } this.codec = MediaCodec.createByCodecName(codecInfo.getName()); MediaFormat format = getMediaFormat(codecAndBitrate, sampleRateHz); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); initBuffers(); addedHeader = false; successfullyFlushed = false; formatChangeReportedOnce = false; } @Override public byte[] processAudioBytes(byte[] input, int offset, int length) { ByteArrayOutputStream outputBytes = new ByteArrayOutputStream(); if (!addedHeader) { try { outputBytes.write(getHeaderBytes()); } catch (IOException e) { logger.atSevere().log("Unable to write bytes into buffer!"); } addedHeader = true; } int startByte = 0; while (startByte < length) { int thisChunkSizeBytes = Math.min(CHUNK_SIZE_BYTES, length - startByte); processAudioBytesInternal( input, offset + startByte, thisChunkSizeBytes, false, outputBytes); startByte += thisChunkSizeBytes; } return outputBytes.toByteArray(); } @Override public byte[] flushAndStop() { ByteArrayOutputStream outputBytes = new ByteArrayOutputStream(); try { processAudioBytesInternal(null, 0, 0, true, outputBytes); // Flush! codec.stop(); } catch (MediaCodec.CodecException e) { logger.atSevere().log("Something went wrong in the underlying codec!"); } codec.release(); return outputBytes.toByteArray(); } // length must be less than or equal to CHUNK_SIZE_BYTES. private void processAudioBytesInternal( byte[] input, int offset, int length, boolean flush, ByteArrayOutputStream outputBytes) { checkArgument( length <= CHUNK_SIZE_BYTES, "length must be less than or equal to CHUNK_SIZE_BYTES!"); boolean processedInput = false; // There are a limited number of buffers allocated in the codec. As long as we're not // holding on to them, they should always be available. Sometimes all buffers will be occupied // by the output and we need to process them before pushing input. Sometimes multiple output // buffers will be available at once. Append them together and return. It is common for // outputBytes to not receive any samples upon returning. MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); // Loop until input is processed and outputs are unavailable. while (!processedInput || flush) { if (!processedInput) { if (flush && successfullyFlushed) { throw new IllegalStateException("Already flushed!"); } // Push the input only once. int inputBufferIndex = codec.dequeueInputBuffer(WAIT_TIME_MICROSECONDS); if (inputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) { if (flush) { // Signal that the input stream is complete. codec.queueInputBuffer( inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); successfullyFlushed = true; } else { // Push audio data into the codec. ByteBuffer inputBuffer = getInputBuffer(inputBufferIndex); inputBuffer.put(input, offset, length); codec.queueInputBuffer(inputBufferIndex, 0, length, 0, 0); } processedInput = true; } } // See if outputs are available. int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, WAIT_TIME_MICROSECONDS); if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { // There will not be an output buffer for every input buffer. } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // Shouldn't happen after the very first output. if (formatChangeReportedOnce) { throw new IllegalStateException("The codec format was unexpectedly changed."); } formatChangeReportedOnce = true; } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { // Shouldn't happen for SDK > 21. updateOutputBuffers(); } else { // Get an output buffer and add it to the stream. ByteBuffer outputBuffer = getOutputBuffer(outputBufferIndex); byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); codec.releaseOutputBuffer(outputBufferIndex, false); try { outputBytes.write(outData); } catch (IOException e) { logger.atSevere().log("Unable to write bytes into buffer!"); } } boolean processedAllOutput = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; if (processedAllOutput) { if (!processedInput) { throw new IllegalStateException("Didn't process input yet."); } break; } } } /** Configure the codec at a specified bitrate for a fixed sample block size. */ private static MediaFormat getMediaFormat(CodecAndBitrate codecAndBitrate, int sampleRateHz) { MediaFormat format = new MediaFormat(); CodecType codecType = lookupCodecType(codecAndBitrate); format.setString(MediaFormat.KEY_MIME, getMime(codecType)); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRateHz); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, BYTES_PER_SAMPLE * CHUNK_SIZE_SAMPLES); if (codecType != CodecType.FLAC) { // FLAC is lossless, we can't request a bitrate. format.setInteger(MediaFormat.KEY_BIT_RATE, codecAndBitrate.getNumber()); } return format; } /** The data does not include a header. Some applications will require one anyhow. */ private byte[] getHeaderBytes() { switch (this.codecType) { case AMRWB: String amrWbHeader = "#!AMR-WB\n"; return amrWbHeader.getBytes(); case FLAC: byte[] noHeader = new byte[0]; return noHeader; case OGG_OPUS: throw new IllegalStateException("Should never happen! Use OggOpusEncoder instead."); case UNSPECIFIED: throw new IllegalStateException("Trying to make header for unspecified codec!"); } return null; } // The following methods are used to resolve differences between SDK versions. private void initBuffers() { if (useDeprecatedEncoder) { inputBuffersPreKitKat = codec.getInputBuffers(); outputBuffersPreKitKat = codec.getOutputBuffers(); } } private ByteBuffer getInputBuffer(int index) { if (useDeprecatedEncoder) { return inputBuffersPreKitKat[index]; } else { return codec.getInputBuffer(index); } } private ByteBuffer getOutputBuffer(int index) { if (useDeprecatedEncoder) { return outputBuffersPreKitKat[index]; } else { return codec.getOutputBuffer(index); } } private void updateOutputBuffers() { if (useDeprecatedEncoder) { outputBuffersPreKitKat = codec.getOutputBuffers(); } } } private static class OggOpusEncoder implements StreamingAudioInternalEncoder { // This is a pointer to the native object that we're working with. Zero when unallocated. private long instance = 0; ImmutableList validSampleRates = ImmutableList.of(8000, 12000, 16000, 24000, 48000); public OggOpusEncoder() {} @Override public void init(int sampleRateHz, CodecAndBitrate codecAndBitrate, boolean allowVbr) throws EncoderException { if (instance != 0) { flushAndStop(); } CodecType codecType = lookupCodecType(codecAndBitrate); if (codecType != CodecType.OGG_OPUS) { throw new RuntimeException("Made OggOpusEncoder for non OGG_OPUS encoding type."); } if (!validSampleRates.contains(sampleRateHz)) { throw new EncoderException( "Opus encoder requires a sample rate of 8kHz, 12kHz, 16kHz, 24kHz, or 48kHz."); } this.instance = init(1 /* Mono audio. */, codecAndBitrate.getNumber(), sampleRateHz, allowVbr); } private native long init(int channels, int bitrate, int sampleRateHz, boolean allowVbr); @Override public byte[] processAudioBytes(byte[] bytes, int offset, int length) { return processAudioBytes(instance, bytes, offset, length); } private native byte[] processAudioBytes(long instance, byte[] samples, int offset, int length); /** * Complete the input stream, return any remaining bits of the output stream, and stop. * This should only be called once. Must be called after init(). * * @return bytes of compressed audio */ @Override public byte[] flushAndStop() { if (instance != 0) { byte[] flushedBytes = flush(instance); free(instance); instance = 0; return flushedBytes; } else { logger.atSevere().log("stop() called multiple times or without call to init()!"); return new byte[0]; } } @Override protected void finalize() throws Throwable { super.finalize(); if (instance != 0) { logger.atSevere().log( "Native OggOpusEncoder resources weren't cleaned up. You must call stop()!"); free(instance); } } private native byte[] flush(long instance); private native void free(long instance); } static { System.loadLibrary("ogg_opus_encoder"); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/gcloudspeech/CloudSpeechSession.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // The CloudSpeechSession streams audio to the server until the endpointer tells it to stop. It // therefore must be repeatedly reopened for continuous transcription. The response observer gets // data back from the server. Our CloudSpeechStreamObserver extracts the speech and the confidence // and passes the data to a SpeechSessionListener, which helps to aggregate // TranscriptionResults and manage the repeatedly reopening sessions. package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.gcloudspeech; import com.google.audio.asr.CloudSpeechSessionParams; import com.google.audio.asr.SpeechRecognitionModelOptions; import com.google.cloud.speech.v1p1beta1.RecognitionConfig; import com.google.cloud.speech.v1p1beta1.SpeechContext; import com.google.cloud.speech.v1p1beta1.SpeechGrpc; import com.google.cloud.speech.v1p1beta1.StreamingRecognitionConfig; import com.google.cloud.speech.v1p1beta1.StreamingRecognizeRequest; import com.google.common.flogger.FluentLogger; import com.google.protobuf.ByteString; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SpeechSession; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SpeechSessionListener; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.asrhelpers.StreamingAudioEncoder; import org.joda.time.Duration; import java.io.IOException; import java.util.ArrayList; import io.grpc.ManagedChannel; import io.grpc.stub.StreamObserver; /** * Lightweight wrapper around the GRPC Google Cloud Speech API. It can handle one streaming * recognition request at a time. */ public class CloudSpeechSession extends SpeechSession { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private SpeechGrpc.SpeechStub speechClient; // Since the speech session times out after 5 minutes, we should try to avoid sessions reaching // approximately that length. If speech begins 4 mins and 30 seconds into the transcription, it // will likely be cut off. Therefore, we close sessions that haven't received any results in an // extended period of time. public static final Duration CLOSE_SESSION_AFTER_EXTENDED_SILENCE_DURATION = Duration.standardMinutes(4).plus(Duration.standardSeconds(30)); /** * The stream observer and cloud interaction functions are marked protected so that they may be * replaced with a local server simulation in testing. */ private CloudSpeechStreamObserver responseObserver; private StreamObserver requestObserver; private final CloudSpeechSessionParams params; private final SpeechSessionListener speechSessionListener; private int sampleRateHz; private boolean stillSendingRequests = false; private final ManagedChannel channel; private StreamingAudioEncoder encoder; private boolean encoderIsRequested; private boolean encoderIsSupported; /* * @param speechSessionListener Listener for recognition responses. * @param sampleRateHz Sample rate of microphone data. * @param channel The channel to use for cloud communication. */ public CloudSpeechSession( CloudSpeechSessionParams params, SpeechSessionListener speechSessionListener, int sampleRateHz, ManagedChannel channel) { this.params = params; this.channel = channel; this.speechSessionListener = speechSessionListener; this.sampleRateHz = sampleRateHz; this.encoder = new StreamingAudioEncoder(); } /** Starts a streaming speech recognition request. */ @Override public synchronized void initImpl( SpeechRecognitionModelOptions modelOptions, int chunkSizeSamples) { if (chunkSizeSamples < 0.050 * sampleRateHz) { logger.atWarning().log( "Your buffer size is less than 50ms, you may have poor performance getting " + "streaming results."); } responseObserver = makeResponseObserver(speechSessionListener); encoderIsRequested = params.getEncoderParams().getEnableEncoder(); encoderIsSupported = StreamingAudioEncoder.isEncoderSupported(params.getEncoderParams().getCodec()); if (usingEncoder()) { try { encoder.init( sampleRateHz, params.getEncoderParams().getCodec(), params.getEncoderParams().getAllowVbr()); } catch (StreamingAudioEncoder.EncoderException | IOException e) { e.printStackTrace(); logger.atSevere().log("Encoder could not be created. Using uncompressed audio."); encoderIsRequested = false; } } initServer(modelOptions); stillSendingRequests = true; } /** Returns true when the encoder is being used. */ public boolean usingEncoder() { return encoderIsRequested && encoderIsSupported; } private CloudSpeechStreamObserver makeResponseObserver( SpeechSessionListener speechSessionListener) { return new CloudSpeechStreamObserver( params.getObserverParams(), speechSessionListener, sessionID()); } /** * Sends an audio buffer to the Cloud Speech Server. * * @param buffer 16 bit LinearPCM byte array. * @param offset first element of buffer to use. * @param count number of elements of buffer to use. * @return true if audio data was processed, false if session was already requested to close. You * should wait for the recognition listener passed into the constructor to receive * OK_TO_TERMINATE before destroying the session. */ @Override public synchronized boolean processAudioBytesImpl(byte[] buffer, int offset, int count) { if (!isStillSendingRequests()) { return false; } if (usingEncoder()) { byte[] encoded = encoder.processAudioBytes(buffer, offset, count); if (encoded.length > 0) { streamToServer(encoded, 0, encoded.length); } } else { streamToServer(buffer, offset, count); } if (CLOSE_SESSION_AFTER_EXTENDED_SILENCE_DURATION.isShorterThan( responseObserver.timeSinceLastServerActivity())) { logger.atInfo().log( "Session #%d scheduled to be ended due to extended silence.", sessionID()); requestCloseSession(); } return true; } private boolean isStillSendingRequests() { return stillSendingRequests && responseObserver.isStillListening(); } /** * Closes the current recognition request on the client end. This does not immediately end the * session. Only once the server acknowledges the closing of the session is communication * complete. */ @Override public synchronized void requestCloseSessionImpl() { if (stillSendingRequests) { stillSendingRequests = false; if (usingEncoder()) { // Get any remaining output from the codec and stop. byte[] data = encoder.flushAndStop(); streamToServer(data, 0, data.length); } closeServer(); } } @Override public boolean requiresNetworkConnection() { return true; } private void initServer(SpeechRecognitionModelOptions modelOptions) { this.speechClient = SpeechGrpc.newStub(channel); requestObserver = speechClient.streamingRecognize(responseObserver); // Build and send a StreamingRecognizeRequest containing the parameters for // processing the audio. SpeechContext speechContext = SpeechContext.getDefaultInstance(); RecognitionConfig.AudioEncoding encodingType = RecognitionConfig.AudioEncoding.LINEAR16; if (usingEncoder()) { switch (encoder.getCodecType()) { case AMRWB: encodingType = RecognitionConfig.AudioEncoding.AMR_WB; break; case FLAC: encodingType = RecognitionConfig.AudioEncoding.FLAC; break; case OGG_OPUS: encodingType = RecognitionConfig.AudioEncoding.OGG_OPUS; break; default: } } ArrayList languageList = new ArrayList<>(); languageList.add("en-US"); RecognitionConfig.Builder configBuilder = RecognitionConfig.newBuilder() .setEncoding(encodingType) .setSampleRateHertz(sampleRateHz) .setAudioChannelCount(1) .setEnableAutomaticPunctuation(true) .setEnableWordConfidence(true) .setEnableWordTimeOffsets(true) // .addAllAlternativeLanguageCodes(languageList) //seems to be switching to english too much .addSpeechContexts(speechContext) .setLanguageCode(modelOptions.getLocale()) .setProfanityFilter(params.getFilterProfanity()) .addSpeechContexts( SpeechContext.newBuilder() .addAllPhrases(modelOptions.getBiasWordsList())); StreamingRecognitionConfig.Builder strbuilder = StreamingRecognitionConfig.newBuilder() .setInterimResults(true) .setSingleUtterance(false); switch (modelOptions.getModel()) { case VIDEO: if (!modelOptions.getLocale().equals("en-US")) { logger.atSevere().log("Only en-US is supported by YouTube Livestream model"); } // configBuilder.setModel("video"); configBuilder.setModel("latest_long"); break; case DICTATION_DEFAULT: configBuilder.setModel("default"); break; } RecognitionConfig config = configBuilder.build(); StreamingRecognitionConfig streamingConfig = strbuilder.setConfig(config).build(); // First request sends the configuration. StreamingRecognizeRequest initial = StreamingRecognizeRequest.newBuilder().setStreamingConfig(streamingConfig).build(); requestObserver.onNext(initial); } private void streamToServer(byte[] buffer, int offset, int count) { StreamingRecognizeRequest request = StreamingRecognizeRequest.newBuilder() .setAudioContent(ByteString.copyFrom(buffer, offset, count)) .build(); requestObserver.onNext(request); } private void closeServer() { if (requestObserver != null) { // Tell the server we're done sending. requestObserver.onCompleted(); requestObserver = null; } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/gcloudspeech/CloudSpeechSessionFactory.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.gcloudspeech; import androidx.annotation.GuardedBy; import com.google.audio.asr.CloudSpeechSessionParams; import com.google.common.flogger.FluentLogger; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SpeechSession; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SpeechSessionFactory; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SpeechSessionListener; import org.joda.time.Duration; import java.util.concurrent.TimeUnit; import io.grpc.ConnectivityState; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Metadata; import io.grpc.stub.MetadataUtils; /** A factory for creating cloud sessions. */ public class CloudSpeechSessionFactory implements SpeechSessionFactory { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final String SERVICE_URL = "speech.googleapis.com"; private static final String HEADER_API_KEY = "X-Goog-Api-Key"; /** Wait 1 second for the preexisting calls to finish. */ private static final Duration TERMINATE_CHANNEL_DURATION = Duration.standardSeconds(1); /** Lock for handling concurrent accesses to the `params` variable. */ private final Object paramsLock = new Object(); @GuardedBy("paramsLock") private CloudSpeechSessionParams params; private String apiKey; private ManagedChannel channel; public CloudSpeechSessionFactory(CloudSpeechSessionParams params, String apiKey) { this.params = params; this.apiKey = apiKey; } @Override public SpeechSession create(SpeechSessionListener listener, int sampleRateHz) { if (this.channel == null) { this.channel = createManagedChannel(apiKey); } else { ensureManagedChannelConnection(); } synchronized (paramsLock) { return new CloudSpeechSession(params, listener, sampleRateHz, channel); } } @Override public void cleanup() { if (channel != null) { channel.shutdown(); try { if (!channel.awaitTermination( TERMINATE_CHANNEL_DURATION.getStandardSeconds(), TimeUnit.SECONDS)) { channel.shutdownNow(); } } catch (InterruptedException e) { logger.atWarning().withCause(e).log("Channel termination failed."); } channel = null; } } public void setParams(CloudSpeechSessionParams params) { synchronized (paramsLock) { this.params = params; } } protected void ensureManagedChannelConnection() { // The channel may stuck at the TRANSIENT_FAILURE state, if so, enter idle to let channel to // trigger creation of a new connection. if (ConnectivityState.TRANSIENT_FAILURE.equals(channel.getState(false))) { logger.atInfo().log("ManagedChannel was in TRANSIENT_FAILURE state."); channel.enterIdle(); } } private ManagedChannel createManagedChannel(String apiKey) { Metadata metadata = new Metadata(); metadata.put(Metadata.Key.of(HEADER_API_KEY, Metadata.ASCII_STRING_MARSHALLER), apiKey); return ManagedChannelBuilder.forTarget(SERVICE_URL) .intercept(MetadataUtils.newAttachHeadersInterceptor(metadata)) .build(); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/gcloudspeech/CloudSpeechStreamObserver.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.gcloudspeech; import com.google.audio.asr.CloudSpeechStreamObserverParams; import com.google.audio.asr.TranscriptionResult; import com.google.cloud.speech.v1p1beta1.SpeechRecognitionAlternative; import com.google.cloud.speech.v1p1beta1.StreamingRecognitionResult; import com.google.cloud.speech.v1p1beta1.StreamingRecognizeResponse; import com.google.cloud.speech.v1p1beta1.StreamingRecognizeResponse.SpeechEventType; import com.google.cloud.speech.v1p1beta1.WordInfo; import com.google.common.base.Optional; import com.google.common.flogger.FluentLogger; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SpeechSessionListener; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.TimeUtil; import org.joda.time.Duration; import org.joda.time.Instant; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import io.grpc.stub.StreamObserver; /** * Parses Cloud Speech API GRPC StreamingRecognizeResponse events into CloudSpeechSessionListener * events. * NOTE: that this object is stateful and needs to be re-instantiated for each streaming * request. * * Threading: All methods that implement the StreamObserver interface are to be called by gRPC. This * is the "results thread" as documented in the RepeatingRecognitionSession. The other public * functions are safe to call from another thread, they are called by CloudSpeechSession in the * current implementation. */ public class CloudSpeechStreamObserver implements StreamObserver { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); // The CloudSpeech API imposes a maximum streaming time of 5 minutes. In order to avoid hitting // this, but still be compatible with singleUtterance = false mode, which is required by some // models, we attempt to close the session after receiving a finalized result after being opened // for MIN_TIME_TO_KEEP_SESSION_OPEN. private static final Duration MIN_TIME_TO_KEEP_SESSION_OPEN = Duration.standardMinutes(4).plus(Duration.standardSeconds(30)); private final SpeechSessionListener speechSessionListener; private final Instant sessionOkToRequestCloseTime; // Class for computing and returning the timestamps. private final TimestampCalculator timestampCalculator; // These variables are accessed through public functions from the recognition thread // (via CloudSpeechSession). private final AtomicBoolean stillListening = new AtomicBoolean(true); private final AtomicReference lastActivityTimestamp = new AtomicReference<>(); /** * Keeps track of time of arrival of first word. Optional.absent() means that the utterance has * not started yet. */ private Optional utteranceStartTime; // Note that only when the results are finalized are the confidences nonzero. private static final float K_CONFIDENCE_NOT_SET = 0.0f; private final CloudSpeechStreamObserverParams params; private final int sessionID; public CloudSpeechStreamObserver( CloudSpeechStreamObserverParams params, SpeechSessionListener speechSessionListener, int sessionID) { this.sessionID = sessionID; this.speechSessionListener = speechSessionListener; this.params = params; this.sessionOkToRequestCloseTime = Instant.now().plus(MIN_TIME_TO_KEEP_SESSION_OPEN); updateLastActivityTimestamp(); this.utteranceStartTime = Optional.absent(); // The timestampCalculator keeps track of the session start time because the finalized word // times are relative to the time of the beginning of the session. this.timestampCalculator = new TimestampCalculator(Instant.now()); } /** Convert the results the speech recognizer gives us into an understandable transcript. */ @Override public void onNext(StreamingRecognizeResponse response) { if (response == null) { return; } updateLastActivityTimestamp(); if (!utteranceStartTime.isPresent()) { utteranceStartTime = Optional.of(Instant.now()); timestampCalculator.reset(); } StringBuilder transcriptString = new StringBuilder(); float highestConfidence = K_CONFIDENCE_NOT_SET; boolean endedWithFinalResult = false; String languageCode = ""; TranscriptionResult.Builder resultBuilder = TranscriptionResult.newBuilder(); // Results are for non-overlapping sections of time, each result may have several possible // transcripts, called "alternatives". for (StreamingRecognitionResult result : response.getResultsList()) { // We use a threshold of 0.5 for stability. In practice, only 0.9 and 0.01 seem to ever come // up, so this hardly seems like it is worth tuning. final float stableConfidenceThreshold = 0.5f; if (params.getRejectUnstableHypotheses() && !result.getIsFinal() && result.getStability() < stableConfidenceThreshold) { continue; } SpeechRecognitionAlternative bestAlternative = result.getAlternativesList().get(0); highestConfidence = bestAlternative.getConfidence(); transcriptString.append(bestAlternative.getTranscript()); for (WordInfo wordInfo : bestAlternative.getWordsList()) { TranscriptionResult.Word.Builder word = TranscriptionResult.Word.newBuilder() .setText(wordInfo.getWord()) .setStartTimestamp(timestampCalculator.getFinalizedStartTimestamp(wordInfo)) .setEndTimestamp(timestampCalculator.getFinalizedEndTimestamp(wordInfo)); if (wordInfo.getConfidence() != K_CONFIDENCE_NOT_SET) { word.setConfidence(wordInfo.getConfidence()); } resultBuilder.addWordLevelDetail(word); } languageCode = result.getLanguageCode(); if (result.getIsFinal()) { endedWithFinalResult = true; break; } } // If the transcript does not have a word list, generate the list of words and their // timestamps from the partial result utterance. if (resultBuilder.getWordLevelDetailCount() == 0) { List unfinalizedTimestamps = timestampCalculator.updateUnfinalizedTimestamps(transcriptString); resultBuilder.addAllWordLevelDetail(unfinalizedTimestamps); } // If result only contains an endpoint event, we will not call onResults or onPartialResults. if (transcriptString.length() > 0) { speechSessionListener.onResults( sessionID, resultBuilder .setText(transcriptString.toString()) .setConfidence(highestConfidence) .setStartTimestamp(TimeUtil.toTimestamp(utteranceStartTime.get())) .setEndTimestamp(TimeUtil.toTimestamp(Instant.now())) .setLanguageCode(languageCode) .build(), endedWithFinalResult); if (endedWithFinalResult) { // Reset the utterance start timer to the uninitialized state. utteranceStartTime = Optional.absent(); } // Request to stop the session if we see a final result. if (endedWithFinalResult && Instant.now().isAfter(sessionOkToRequestCloseTime)) { logger.atInfo().log("Session #%d scheduled to close to avoid timeout.", sessionID); stopListening(); speechSessionListener.onDoneListening(sessionID); } } if (response.getSpeechEventType() == SpeechEventType.END_OF_SINGLE_UTTERANCE) { stopListening(); speechSessionListener.onDoneListening(sessionID); } } @Override public void onError(Throwable t) { updateLastActivityTimestamp(); stopListening(); speechSessionListener.onSessionFatalError(sessionID, t); } @Override public void onCompleted() { updateLastActivityTimestamp(); speechSessionListener.onOkToTerminateSession(sessionID); } // This method is needed to communicate to the CloudSpeechSession that audio should no longer // be sent quickly. Without this, the "stop listening" signal has to propagate through the // SpeechSessionListener and the RepeatingRecognitionSession in order to tell the session to // stop accepting audio. Without this, audio buffers can be lost. // // Will be called though CloudSpeechSession (the recognition thread). public boolean isStillListening() { return stillListening.get(); } // Will be called though CloudSpeechSession (the recognition thread). public Duration timeSinceLastServerActivity() { return new Duration(lastActivityTimestamp.get(), Instant.now()); } private void stopListening() { stillListening.set(false); } /** Update the last activity timestamp. This should be called whenever the session * changes in any way. */ private void updateLastActivityTimestamp() { lastActivityTimestamp.set(Instant.now()); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/google/gcloudspeech/TimestampCalculator.java ================================================ /* * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.gcloudspeech; import com.google.audio.asr.TranscriptionResult; import com.google.cloud.speech.v1p1beta1.WordInfo; import com.google.common.base.Splitter; import com.google.protobuf.Timestamp; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.TimeUtil; import org.joda.time.Duration; import org.joda.time.Instant; import java.util.ArrayList; import java.util.List; /** * Calculates unfinalized and finalized timestamps and adds them to the word-level details of * utterances. Finalized timestamps are simply copied from the corresponding timestamps returned by * the cloud. Unfinalized timestamps do not exist in the response returned by the cloud, so they are * computed from the returned transcript instead. */ public class TimestampCalculator { /** * Keeps track of the session start time because the finalized word times are relative to the time * of the beginning of the session. */ private final Instant sessionStartTime; /** * Stores the time instants of each word in the un-finalized utterance. As the utterance is * updated with more words, this array marks the time of the new words. */ private final ArrayList unfinalizedWordInstants = new ArrayList<>(); private static final int NANOS_PER_MILLIS = 1_000_000; public TimestampCalculator(Instant newSessionStartTime) { this.sessionStartTime = newSessionStartTime; } public Timestamp getFinalizedStartTimestamp(WordInfo wordInfo) { Duration startOffset = Duration.standardSeconds(wordInfo.getStartTime().getSeconds()) .plus(Duration.millis(wordInfo.getStartTime().getNanos() / NANOS_PER_MILLIS)); return TimeUtil.toTimestamp(sessionStartTime.plus(startOffset)); } public Timestamp getFinalizedEndTimestamp(WordInfo wordInfo) { Duration endOffset = Duration.standardSeconds(wordInfo.getEndTime().getSeconds()) .plus(Duration.millis(wordInfo.getEndTime().getNanos() / NANOS_PER_MILLIS)); return TimeUtil.toTimestamp(sessionStartTime.plus(endOffset)); } public void reset() { unfinalizedWordInstants.clear(); } public List updateUnfinalizedTimestamps( StringBuilder transcriptString) { // Generate the list of words and their timestamps from the partial result utterance. // This implementation doesn't require every "word" to have a timestamp; instead it is // timestamping the smallest logical chunk returned by the ASR. // The algorithm splits by spaces as a convenience to count how many new words have come in. // This works for languages that has spaces between logical groups of character (such as words). // For languages that don't have spaces, it treats the group of characters as one timestamp. List wordList = Splitter.onPattern("\\s+").splitToList(transcriptString.toString().trim()); // Compute time instants for the newly occurring words. // We do not change the times of previously computed words. for (int i = unfinalizedWordInstants.size(); i < wordList.size(); i++) { unfinalizedWordInstants.add(Instant.now()); } // Use the computed words and computed time instances to build the word-level detail. // Since the previous loop guaranteed that the size of the word instants array is at least // as long as the list of words, we can safely index into the array. List wordLevelDetailList = new ArrayList<>(); for (int i = 0; i < wordList.size(); i++) { // We set both the start and end timestamps to the same value because we don't know when // the word actually begins and ends because of potential pauses between words. Instant timeInstant = unfinalizedWordInstants.get(i); TranscriptionResult.Word word = TranscriptionResult.Word.newBuilder() .setText(wordList.get(i)) .setStartTimestamp(TimeUtil.toTimestamp(timeInstant)) .setEndTimestamp(TimeUtil.toTimestamp(timeInstant)) .build(); wordLevelDetailList.add(word); } return wordLevelDetailList; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/vad/VadGateSpeechPolicy.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.vad; import android.content.Context; import android.util.Log; import com.konovalov.vad.Vad; import com.konovalov.vad.VadListener; import com.konovalov.vad.config.FrameSize; import com.konovalov.vad.config.Mode; import com.konovalov.vad.config.Model; import com.konovalov.vad.config.SampleRate; import com.konovalov.vad.models.VadModel; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.google.asr.SpeechDetectionPolicy; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; /** A speech detector that always reports hearing speech. */ public class VadGateSpeechPolicy implements SpeechDetectionPolicy { public final String TAG = "WearLLM_VadGateService"; private Context mContext; private Vad vad; private VadModel vadModel; private boolean isCurrentlySpeech; public VadGateSpeechPolicy(Context context){ mContext = context; isCurrentlySpeech = false; } //custom - divide by 3, gives us best of both worlds - vosk runs best at ~0.2 second buffer, this runs best at 512-1024 size frame, so we run at 0.192second buffer and divide by 3 public void startVad(int blockSizeSamples){ vad = Vad.builder(); blockSizeSamples = blockSizeSamples / 3; Log.d(TAG, "VAD looking for block size samples: " + blockSizeSamples); //find the proper frame size FrameSize fsToUse = null; for (FrameSize fs : FrameSize.values()){ if (fs.getValue() == blockSizeSamples){ fsToUse = fs; break; } } if (fsToUse == null){ Log.e(TAG, "Frame size not supported by VAD, exiting."); return; } vadModel = vad.setModel(Model.SILERO_DNN) .setSampleRate(SampleRate.SAMPLE_RATE_16K) .setFrameSize(fsToUse) // .setMode(Mode.VERY_AGGRESSIVE) .setMode(Mode.AGGRESSIVE) .setSilenceDurationMs(1350) .setSpeechDurationMs(50) .setContext(mContext) .build(); Log.d(TAG, "VAD init'ed."); } @Override public boolean shouldPassAudioToRecognizer() { return isCurrentlySpeech; } @Override public void init(int blockSizeSamples) { startVad(blockSizeSamples); } @Override public void reset() {} public short [] bytesToShort(byte[] bytes) { short[] shorts = new short[bytes.length/2]; // to turn bytes to shorts as either big endian or little endian. ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts); return shorts; } @Override public void processAudioBytes(byte[] bytes, int offset, int length) { short [] audioBytesFull = bytesToShort(bytes); int windowLen = audioBytesFull.length / 3; for (int i = 0; i < 3; i++) { int moffset = i * windowLen; short [] audioBytesPartial = Arrays.copyOfRange(audioBytesFull, moffset, moffset + windowLen); vadModel.setContinuousSpeechListener(audioBytesPartial, new VadListener() { @Override public void onSpeechDetected() { //speech detected! // Log.d(TAG, "Speech detected."); isCurrentlySpeech = true; } @Override public void onNoiseDetected() { //noise detected! // Log.d(TAG, "Noise detected!"); isCurrentlySpeech = false; } }); } } @Override public void stop() { vadModel.close(); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/speechrecognition/vosk/SpeechRecVosk.java ================================================ package com.teamopensmartglasses.smartglassesmanager.speechrecognition.vosk; //Vosk ASR import android.content.Context; import android.os.Handler; import android.util.Base64; import android.util.Log; import com.teamopensmartglasses.smartglassesmanager.comms.MessageTypes; import com.teamopensmartglasses.augmentoslib.events.SpeechRecOutputEvent; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.SpeechRecFramework; import org.greenrobot.eventbus.EventBus; import org.json.JSONException; import org.json.JSONObject; import org.vosk.LibVosk; import org.vosk.LogLevel; import org.vosk.Model; import org.vosk.Recognizer; import org.vosk.android.RecognitionListener; import org.vosk.android.StorageService; import java.io.InputStream; import java.util.Dictionary; import java.util.Hashtable; import java.util.Locale; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.subjects.PublishSubject; public class SpeechRecVosk extends SpeechRecFramework implements RecognitionListener { public String TAG = "WearableAi_SpeechRecVosk"; private String languageModelPath; private boolean isBaseLanguage = true; private Context mContext; //the current phrase we are receiving private boolean newPhrase = true; private Model model; //private SpeechService speechService; //private SpeechStreamService speechStreamService; private SpeechStreamQueueServiceVosk speechStreamService; // private PhraseRepository mPhraseRepository = null; private VoskAudioBytesStream voskAudioBytesStream; //private PipedOutputStream audioAdderStreamVosk; //private InputStream audioSenderStreamVosk; private int audioSenderStreamVoskSize; private BlockingQueue audioSenderStreamVosk; private Handler main_handler; //receive/send data stream PublishSubject dataObservable; Disposable dataSub; //receive audio stream PublishSubject audioObservable; Disposable audioSub; //languages String baseLanguage = "english"; public static Dictionary supportedLanguages = new Hashtable(); public SpeechRecVosk(Context context){ mContext = context; this.isBaseLanguage = true;// isBaseLanguage; //setup languages supportedLanguages.put("english", new NaturalLanguage("english", "en", "model-en-us", Locale.ENGLISH)); //english this.languageModelPath = supportedLanguages.get(baseLanguage).getModelLocation(); //languageModelPath; // supportedLanguages.put("french", new NaturalLanguage("french", "fr", "model-fr-small", Locale.FRENCH)); //french //supportedLanguages.add(new NaturalLanguage("chinese", "zh", "model-cn-small", Locale.CHINESE)); //chinese //supportedLanguages.add(new NaturalLanguage("italian", "it", "model-it-small", Locale.ITALIAN)); //italian //supportedLanguages.add(new NaturalLanguage("japanese", "ja", "model-jp-small", Locale.JAPANESE)); //japanese //to save trancript // this.mPhraseRepository = mPhraseRepository; //receive/send data // this.dataObservable = dataObservable; // dataSub = this.dataObservable.subscribe(i -> handleDataStream(i)); // setupEventBusSubscribers(); //receive audio //this.audioObservable = audioObservable; //audioSub = this.audioObservable.subscribe(i -> handleDataStream(i)); //setup the object which will pass audio bytes to vosk // audioSenderStreamVoskSize = (int) (16000 * 2 * 0.2); audioSenderStreamVoskSize = (int) (16000 * 2 * 0.192); // audioSenderStreamVoskSize = (int) (16000 * 2 * 0.064); audioSenderStreamVosk = new ArrayBlockingQueue(audioSenderStreamVoskSize); } @Override public void start(){ //start vosk ASR LibVosk.setLogLevel(LogLevel.INFO); initModel(); //start recognizing audio after delay, must first wait for model to load main_handler = new Handler(); final int delay = 500; main_handler.postDelayed(new Runnable() { public void run() { if (model != null){ recognizeSpeech(); } else { main_handler.postDelayed(this, delay); } } }, delay); } private void setupEventBusSubscribers(){ EventBus.getDefault().register(this); } private void initModel() { Log.d(TAG, "Initing ASR model..."); StorageService.unpack(mContext, languageModelPath, "model", (model) -> { this.model = model; }, (exception) -> setErrorState("Failed to unpack the model: " + exception.getMessage())); Log.d(TAG, "ASR Model loaded."); } private void setErrorState(String message) { Log.d(TAG, "VOSK error: " + message); } private void recognizeSpeech() { if (speechStreamService != null) { speechStreamService.stop(); speechStreamService = null; } else { Log.d(TAG, "VOSK MAKE RECOGNIZER"); Recognizer rec = new Recognizer(model, 16000.0f); Log.d(TAG, "VOSK MAKE SPEECH SERVICE"); //speechService = new SpeechService(rec, 16000.0f); //6416 is hard coded - same as chunk_len - size of buffer used on ASG speechStreamService = new SpeechStreamQueueServiceVosk(rec, audioSenderStreamVosk, 16000.0f); Log.d(TAG, "VOSK START LISTENING"); //speechService.startListening(rec); speechStreamService.start(this); } } private void pause(boolean checked) { // if (speechStreamService != null) { // speechStreamService.setPause(checked); // } } public void destroy() { Log.d(TAG, "Destroying VOSK"); EventBus.getDefault().unregister(this); if (speechStreamService != null) { speechStreamService.stop(); } } //receive audio and send to vosk private void handleDataStream(JSONObject data){ try { String dataType = data.getString(MessageTypes.MESSAGE_TYPE_LOCAL); if (dataType.equals(MessageTypes.AUDIO_CHUNK_DECRYPTED)){ String encodedPlainData = data.getString(MessageTypes.AUDIO_DATA); byte [] decodedPlainData = Base64.decode(encodedPlainData, Base64.DEFAULT); audioSenderStreamVosk.put(decodedPlainData); } } catch (InterruptedException | JSONException e) { setErrorState(e.getMessage()); } } //make our own InputStream class we can fill with audio to pass to vosk class VoskAudioBytesStream extends InputStream { public byte [] data; public int read() { byte [] tmp = {0x01, 0x01}; return 0; } public void write(byte [] inputData){ data = inputData; } } //vosk listener implementation @Override public void onResult(String hypothesis) { //start recognizing audio after delay, must first wait for model to load main_handler.post(new Runnable() { public void run() { handleResult(hypothesis); } }); } public void handleResult(String hypothesis){ long transcriptTime = System.currentTimeMillis(); handleTranscript(hypothesis, MessageTypes.FINAL_TRANSCRIPT, transcriptTime); } public void handleTranscript(String hypothesis, String transcriptType, long transcriptTime){ //save transcript then send to other services in app try { //Below, we do a parsing of Vosk's silly string output //https://github.com/alphacep/vosk-android-demo/issues/81 JSONObject voskResponse = new JSONObject(hypothesis); String transcript; //to send to other services JSONObject transcriptObj = new JSONObject(); //different message types depending on whether or not this is the base language or a foreign language if (transcriptType.equals(MessageTypes.FINAL_TRANSCRIPT)){ transcript = voskResponse.getString("text"); //set event bus type if (isBaseLanguage) { transcriptObj.put(MessageTypes.MESSAGE_TYPE_LOCAL, transcriptType); } else { transcriptObj.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.FINAL_TRANSCRIPT_FOREIGN); } } else if (transcriptType.equals(MessageTypes.INTERMEDIATE_TRANSCRIPT)) { transcript = voskResponse.getString("partial"); if (isBaseLanguage) { transcriptObj.put(MessageTypes.MESSAGE_TYPE_LOCAL, transcriptType); } else { transcriptObj.put(MessageTypes.MESSAGE_TYPE_LOCAL, MessageTypes.INTERMEDIATE_TRANSCRIPT_FOREIGN); } } else { return; } //don't save null or empty transcripts if (transcript == null || transcript.trim().isEmpty()){ return; } //don't save transcripts that always appear incorrect String [] badHitWords = {"huh", "her", "hit", "cut", "this", "if", "but", "by", "hi", "ha", "a", "the", "det", "it", "you", "he"}; for (int i = 0; i < badHitWords.length; i++){ if (transcript.equals(badHitWords[i])){ return; } } if (isBaseLanguage) { if (newPhrase) { // currPhrase = PhraseCreator.init("transcript_ASG", mContext, mPhraseRepository); newPhrase = false; } //update the current phrase // PhraseCreator.create(currPhrase, transcript, mContext, mPhraseRepository); //save transcript if final if (transcriptType.equals(MessageTypes.FINAL_TRANSCRIPT)) { newPhrase = true; } // transcriptObj.put(MessageTypes.TRANSCRIPT_ID, currPhrase.getId()); // transcriptObj.put(MessageTypes.TIMESTAMP, transcriptTime); } // transcriptObj.put(MessageTypes.TRANSCRIPT_TEXT, transcript); // Log.d(TAG, "VOSK SENDING: "); // Log.d(TAG, transcriptObj.toString()); // dataObservable.onNext(transcriptObj); // EventBus.getDefault().post(new SendableIntentEvent(transcriptObj)); //post the event bus event if (transcriptType.equals(MessageTypes.FINAL_TRANSCRIPT)) { EventBus.getDefault().post(new SpeechRecOutputEvent(transcript, transcriptTime, true)); } else { EventBus.getDefault().post(new SpeechRecOutputEvent(transcript, transcriptTime, false)); } } catch (JSONException e){ e.printStackTrace(); } } @Override public void onFinalResult(String hypothesis) { if (speechStreamService != null) { speechStreamService = null; } } @Override public void onPartialResult(String hypothesis) { long transcriptTime = System.currentTimeMillis(); handleTranscript(hypothesis, MessageTypes.INTERMEDIATE_TRANSCRIPT, transcriptTime); } @Override public void onError(Exception e) { setErrorState(e.getMessage()); } @Override public void onTimeout() { Log.d(TAG, "VOSK: timeout"); } // @Subscribe // public void onVoskAudioChunkNewEvent(VoskAudioChunkNewEvent receivedEvent){ // try { // audioSenderStreamVosk.put(receivedEvent.thisChunk); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } @Override public void ingestAudioChunk(byte[] audioChunk) { // Log.d(TAG, "Got chunk Vosk"); try { audioSenderStreamVosk.put(audioChunk); } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/AndroidPhoneTestGlasses.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; // this is the test device, when someone runs the Android smart glasses thing client on an Android phone public class AndroidPhoneTestGlasses extends SmartGlassesDevice { public AndroidPhoneTestGlasses() { deviceModelName = "AndroidPhoneTestGlasses"; anySupport = true; fullSupport = true; glassesOs = SmartGlassesOperatingSystem.ANDROID_OS_GLASSES; hasDisplay = true; hasSpeakers = true; hasCamera = true; hasInMic = true; hasOutMic = true; weight = 160; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/AudioWearable.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public class AudioWearable extends SmartGlassesDevice { public AudioWearable() { deviceModelName = "Audio Wearable"; deviceIconName = "bluetooth_earpiece"; anySupport = true; fullSupport = true; glassesOs = SmartGlassesOperatingSystem.AUDIO_WEARABLE_GLASSES; hasDisplay = false; hasSpeakers = false; //set as false because we want to do this from ASP hasCamera = false; hasInMic = false; //set as false because we want to do this from ASP hasOutMic = false; useScoMic = true; weight = 14; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/EngoTwo.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public class EngoTwo extends SmartGlassesDevice { public EngoTwo() { deviceModelName = "Engo2 by ActiveLook"; deviceIconName = "engo_two"; anySupport = true; fullSupport = true; glassesOs = SmartGlassesOperatingSystem.ACTIVELOOK_OS_GLASSES; hasDisplay = true; hasSpeakers = false; hasCamera = false; hasInMic = false; hasOutMic = false; weight = 37; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/EvenRealitiesG1.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public class EvenRealitiesG1 extends SmartGlassesDevice { public EvenRealitiesG1() { deviceModelName = "Even Realities G1"; deviceIconName = "evenrealities_g1"; anySupport = false; fullSupport = false; glassesOs = SmartGlassesOperatingSystem.EVENREALITIES_G1_MCU_OS_GLASSES; hasDisplay = true; hasSpeakers = false; hasCamera = false; hasInMic = true; hasOutMic = false; useScoMic = false; weight = 38; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/InmoAirOne.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; // these glasses are pretty bad. There's no home button/gesture... the text/font size is all off android specs. Arms are big. The company has zero customer support. We might drop support for these - cayden public class InmoAirOne extends SmartGlassesDevice { public InmoAirOne() { deviceModelName = "Inmo Air 1"; deviceIconName = "inmo_air"; anySupport = true; fullSupport = false; glassesOs = SmartGlassesOperatingSystem.ANDROID_OS_GLASSES; hasDisplay = true; hasSpeakers = true; hasCamera = true; hasInMic = true; hasOutMic = false; //unknown weight = 76; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/InmoGo.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public class InmoGo extends SmartGlassesDevice { public InmoGo() { deviceModelName = "INMO GO"; deviceIconName = "inmo_go"; anySupport = false; fullSupport = false; glassesOs = SmartGlassesOperatingSystem.INMO_GO_MCU_OS_GLASSES; hasDisplay = true; hasSpeakers = false; hasCamera = false; hasInMic = true; hasOutMic = false; useScoMic = false; weight = 38; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/SmartGlassesDevice.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; import java.io.Serializable; public class SmartGlassesDevice implements Serializable { public String deviceModelName; public String deviceIconName; public boolean anySupport; public boolean fullSupport; public SmartGlassesOperatingSystem glassesOs; public boolean hasDisplay; public boolean hasSpeakers; public boolean hasCamera; public boolean hasInMic; public boolean hasOutMic; public boolean useScoMic; public double weight; public int connectionState = -1; //0 is not connected, 1 is trying to connect, 2 is connected public String getDeviceModelName() { return deviceModelName; } public void setDeviceModelName(String deviceModelName) { this.deviceModelName = deviceModelName; } public String getDeviceIconName() { return deviceIconName; } public void setDeviceIconName(String deviceIconName) { this.deviceIconName = deviceIconName; } public boolean getAnySupport() { return anySupport; } public void setAnySupport(boolean anySupport) { this.anySupport = anySupport; } public boolean getFullSupport() { return fullSupport; } public void setFullSupport(boolean fullSupport) { this.fullSupport = fullSupport; } public SmartGlassesOperatingSystem getGlassesOs() { return glassesOs; } public void setGlassesOs(SmartGlassesOperatingSystem glassesOs) { this.glassesOs = glassesOs; } public boolean getHasDisplay() { return hasDisplay; } public void setHasDisplay(boolean hasDisplay) { this.hasDisplay = hasDisplay; } public boolean getHasSpeakers() { return hasSpeakers; } public void setHasSpeakers(boolean hasSpeakers) { this.hasSpeakers = hasSpeakers; } public boolean getHasCamera() { return hasCamera; } public void setHasCamera(boolean hasCamera) { this.hasCamera = hasCamera; } public boolean getHasInMic() { return hasInMic; } public void setHasInMic(boolean hasInMic) { this.hasInMic = hasInMic; } public boolean getHasOutMic() { return hasOutMic; } public void setHasOutMic(boolean hasOutMic) { this.hasOutMic = hasOutMic; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public int getConnectionState() { return connectionState; } public void setConnectionState(int connectionState) { this.connectionState = connectionState; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public enum SmartGlassesOperatingSystem { ANDROID_OS_GLASSES, ACTIVELOOK_OS_GLASSES, ULTRALITE_MCU_OS_GLASSES, EVENREALITIES_G1_MCU_OS_GLASSES, INMO_GO_MCU_OS_GLASSES, AUDIO_WEARABLE_GLASSES } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/TCLRayNeoXTwo.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public class TCLRayNeoXTwo extends SmartGlassesDevice { public TCLRayNeoXTwo() { deviceModelName = "TCL RayNeo X2"; deviceIconName = "tcl_rayneo_x_two"; anySupport = true; fullSupport = false; glassesOs = SmartGlassesOperatingSystem.ANDROID_OS_GLASSES; hasDisplay = true; hasSpeakers = true; hasCamera = true; hasInMic = true; hasOutMic = false; //unknown weight = 120; //unknown } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/VuzixShield.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public class VuzixShield extends SmartGlassesDevice { public VuzixShield() { deviceModelName = "Vuzix Shield"; deviceIconName = "vuzix_shield"; anySupport = true; fullSupport = true; glassesOs = SmartGlassesOperatingSystem.ANDROID_OS_GLASSES; hasDisplay = true; hasSpeakers = true; hasCamera = true; hasInMic = true; hasOutMic = true; weight = 140; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/VuzixUltralite.java ================================================ package com.teamopensmartglasses.smartglassesmanager.supportedglasses; public class VuzixUltralite extends SmartGlassesDevice { public VuzixUltralite() { deviceModelName = "Vuzix Ultralite"; deviceIconName = "vuzix_ultralite"; anySupport = true; fullSupport = true; glassesOs = SmartGlassesOperatingSystem.ULTRALITE_MCU_OS_GLASSES; hasDisplay = true; hasSpeakers = false; hasCamera = false; hasInMic = false; hasOutMic = false; useScoMic = true; weight = 38; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/texttospeech/TextToSpeechSystem.java ================================================ package com.teamopensmartglasses.smartglassesmanager.texttospeech; import android.content.Context; import android.media.AudioManager; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.speech.tts.UtteranceProgressListener; import android.util.Log; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.PauseAsrEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.ScoStartEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.TextToSpeechEvent; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import java.util.HashMap; import java.util.Locale; import java.util.UUID; public class TextToSpeechSystem { private final String TAG = "WearableAi_TextToSpeechSystem"; private Context mContext; public boolean isLoaded = false; private TextToSpeech ttsModel; private boolean sco; public TextToSpeechSystem(Context context){ this.mContext = context; this.sco = false; EventBus.getDefault().register(this); } public void useSco(boolean useSco){ this.sco = useSco; } // public void setup(Locale language){ public void setup(){ Locale language = Locale.ENGLISH; ttsModel = new TextToSpeech(mContext, status -> { if (status == TextToSpeech.SUCCESS) { ttsModel.setLanguage(language); // ttsModel.setSpeechRate(1.6f); // ttsModel.setPitch(0.8f); // ttsModel.setAudioAttributes(new AudioAttributes.Builder() // .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) // .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) // .build()); // Log.d("TextToSpeech","TTS Model initialized"); this.isLoaded = true; // Log.d(TAG, ttsModel.getVoices().toString()); Log.d(TAG, ttsModel.getDefaultEngine()); } else { // Log.d(TAG, "TTS failed with code: " + status); } }); } //default speak (english) public void speak(String text){ speak(text, Locale.ENGLISH) ; } public void speak(String text, Locale locale){ Log.d(TAG, "TTS speaking text: " + text); Log.d(TAG, "TTS speaking this language: " + locale.toString()); if (this.isLoaded){ //setup memory of this tts HashMap params = new HashMap<>(); String utteranceId = UUID.randomUUID().toString(); params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId); // TTS engine is initialized successfully int result = ttsModel.setLanguage(locale); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TAG, "Language is not supported or missing data: " + locale); return; } ttsModel.setOnUtteranceProgressListener(new UtteranceProgressListener() { @Override public void onStart(String utteranceId) { // TTS starts speaking EventBus.getDefault().post(new PauseAsrEvent(true)); } @Override public void onDone(String utteranceId) { EventBus.getDefault().post(new PauseAsrEvent(false)); } @Override public void onError(String utteranceId) { // Handle TTS error } }); if (sco) { Bundle ttsParams = new Bundle(); ttsParams.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, AudioManager.STREAM_VOICE_CALL); ttsParams.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, 1.0f); ttsModel.speak(text, TextToSpeech.QUEUE_FLUSH, ttsParams, utteranceId); } else { ttsModel.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId); } } else { Log.d(TAG, "TTS failed because not loaded."); } } public void destroy(){ EventBus.getDefault().unregister(this); if (ttsModel != null) { ttsModel.shutdown(); } // Log.d("TextToSpeech","TTS destroyed"); } @Subscribe public void handleTtsEvent(TextToSpeechEvent event) { String languageString = event.language; Locale language = Locale.ENGLISH; // Default to English switch (languageString.toLowerCase()) { case "english": language = Locale.ENGLISH; break; case "chinese": language = Locale.CHINESE; // or Locale.SIMPLIFIED_CHINESE for more specificity break; case "chinese (pinyin)": language = Locale.CHINESE; // or Locale.SIMPLIFIED_CHINESE for more specificity break; case "italian": language = Locale.ITALIAN; break; case "japanese": language = Locale.JAPANESE; break; case "spanish": language = new Locale("es", "ES"); break; case "russian": language = new Locale("ru", "RU"); break; case "dutch": language = new Locale("nl", "NL"); break; case "hebrew": language = new Locale("iw", "IL"); break; default: // Log or alert the user that the language is not supported for direct Locale constants Log.d(TAG, "Language not supported by TTS: " + languageString); break; } speak(event.text, language); } @Subscribe public void handleScoEvent(ScoStartEvent event) { useSco(event.scoStart); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/utils/AES.java ================================================ package com.teamopensmartglasses.smartglassesmanager.utils; import android.util.Base64; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import android.util.Log; public class AES { private static final String TAG = "WearableIntelligenceSystem_AES"; private static SecretKeySpec secretKey; private static byte[] key; public static void setKey(String myKey) { MessageDigest sha = null; try { key = myKey.getBytes("UTF-8"); sha = MessageDigest.getInstance("SHA-1"); key = sha.digest(key); key = Arrays.copyOf(key, 16); secretKey = new SecretKeySpec(key, "AES"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } public static String encrypt(String strToEncrypt, String secret) { try { setKey(secret); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return Base64.encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8")), Base64.DEFAULT); } catch (Exception e) { Log.d(TAG, "Error while encrypting: " + e.toString()); } return null; } //bytes version public static byte [] encrypt(byte [ ] bytesToEncrypt, String secret) { try { setKey(secret); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(bytesToEncrypt); } catch (Exception e) { Log.d(TAG, "Error while encrypting: " + e.toString()); } return null; } //String version public static String decrypt(String strToDecrypt, String secret) { try { Log.d(TAG, "Secret key is: " + secret); setKey(secret); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7PADDING"); cipher.init(Cipher.DECRYPT_MODE, secretKey); return new String(cipher.doFinal(Base64.decode(strToDecrypt, Base64.DEFAULT))); } catch (Exception e) { Log.d(TAG, "Error while decrypting: " + e.toString()); } return null; } //bytes version public static byte [] decrypt(byte [] bytesToDecrypt, String secret) { try { setKey(secret); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7PADDING"); cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(bytesToDecrypt); } catch (Exception e) { Log.d(TAG, "Error while decrypting: " + e.toString()); } return null; } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/utils/BitmapJavaUtils.java ================================================ package com.teamopensmartglasses.smartglassesmanager.utils; import java.io.File; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; public class BitmapJavaUtils { private static final String TAG = "WearableAi_BitmapJavaUtils"; public static Bitmap loadImageFromStorage(String path){ File imgFile = new File(path); if(imgFile.exists()){ Bitmap myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath()); return myBitmap; } else { Log.d(TAG, "Image doesn't exist"); return null; } } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/utils/NetworkUtils.java ================================================ package com.teamopensmartglasses.smartglassesmanager.utils; import android.util.Log; import java.net.DatagramSocket; import java.net.DatagramPacket; import android.net.wifi.WifiManager; import java.net.InetAddress; import android.content.Context; import android.net.wifi.WifiInfo; import android.util.Pair; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Enumeration; import java.net.NetworkInterface; import java.net.SocketException; import java.net.InterfaceAddress; import java.net.UnknownHostException; import java.lang.reflect.Method; import java.util.List; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; // see https://stackoverflow.com/questions/14080573/getting-wifi-broadcast-address-in-android-wifi-hotspot for much of this code public class NetworkUtils { public static final String TAG = "WearableAi_NetworkUtils"; public static void sendBroadcast(String messageStr, DatagramSocket adv_socket, int port, Context context){ try { byte[] sendData = messageStr.getBytes(); InetAddress my_ip; if (isHotspotOn(context)){ String my_ip_hs = getHotspotIpAddress(); my_ip = InetAddress.getByName(my_ip_hs); } else { my_ip = getIpAddress(); } InetAddress bca_ip = getBroadcastAddress(my_ip); if (bca_ip == null){ //this probably means we aren't connect to or hosting WiFi //but, some phones, even when wifi tether is on, need this, so let's try: my_ip = getIpAddress(); bca_ip = getBroadcastAddress(my_ip); if (bca_ip == null){ Log.d(TAG, "Broadcast address is null"); return; } } DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bca_ip, port); adv_socket.send(sendPacket); } catch (IOException e){ Log.d(TAG, "FAILED TO SEND BROADCAST"); e.printStackTrace(); return ; } } public static InetAddress getBroadcast(InetAddress inetAddr) { NetworkInterface temp; InetAddress iAddr = null; try { temp = NetworkInterface.getByInetAddress(inetAddr); List addresses = temp.getInterfaceAddresses(); for (InterfaceAddress inetAddress: addresses) iAddr = inetAddress.getBroadcast(); return iAddr; } catch (SocketException e) { e.printStackTrace(); } return null; } public static boolean isHotspotOn(Context context){ WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); Method[] wmMethods = wifi.getClass().getDeclaredMethods(); for (Method method: wmMethods) { if (method.getName().equals("isWifiApEnabled")) { try { if ((Boolean) method.invoke(wifi)) { // isInetConnOn = true; // iNetMode = 2; return true; } else { return false; } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } return false; } public static InetAddress getIpAddress() { InetAddress inetAddress = null; InetAddress myAddr = null; try { for (Enumeration networkInterface = NetworkInterface .getNetworkInterfaces(); networkInterface.hasMoreElements();) { NetworkInterface singleInterface = networkInterface.nextElement(); for (Enumeration IpAddresses = singleInterface.getInetAddresses(); IpAddresses .hasMoreElements();) { inetAddress = IpAddresses.nextElement(); if (!inetAddress.isLoopbackAddress() && (singleInterface.getDisplayName() .contains("wlan0") || singleInterface.getDisplayName().contains("eth0") || singleInterface.getDisplayName().contains("ap0"))) { myAddr = inetAddress; } } } } catch (SocketException ex) { Log.e(TAG, ex.toString()); } return myAddr; } public static String getHotspotIpAddress() { String ip = ""; List> ipAddys = new ArrayList>(); String [] hotspots = new String [] {"swlan", "ap", "wlan1", "wlan"}; try { Enumeration enumNetworkInterfaces = NetworkInterface .getNetworkInterfaces(); while (enumNetworkInterfaces.hasMoreElements()) { NetworkInterface networkInterface = enumNetworkInterfaces .nextElement(); Enumeration enumInetAddress = networkInterface .getInetAddresses(); while (enumInetAddress.hasMoreElements()) { InetAddress inetAddress = enumInetAddress.nextElement(); if (inetAddress.isSiteLocalAddress()){ ip = inetAddress.getHostAddress(); ipAddys.add(new Pair(ip, networkInterface.getName())); } } } } catch (SocketException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } //go through all the ip addresses and choose the one most likely to be the hotspot for (int i = 0; i < hotspots.length; i++) { for (int j = 0; j < ipAddys.size(); j++) { if (ipAddys.get(j).second.contains(hotspots[i])){ return ipAddys.get(j).first; } } } return ip; } public static InetAddress getBroadcastAddress(InetAddress inetAddr) { NetworkInterface temp; InetAddress iAddr = null; try { temp = NetworkInterface.getByInetAddress(inetAddr); List addresses = temp.getInterfaceAddresses(); for (InterfaceAddress inetAddress: addresses) iAddr = inetAddress.getBroadcast(); return iAddr; } catch (SocketException e) { e.printStackTrace(); Log.d(TAG, "getBroadcast" + e.getMessage()); } catch (NullPointerException e){ Log.d(TAG, "Null pointer on getBroadcastAdress, probably means we arent' connected to wifi AND we don't have a live wifi hotspot"); return null; } return null; } public static String getLocalIpAddress(Context context) throws UnknownHostException { WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); assert wifiManager != null; WifiInfo wifiInfo = wifiManager.getConnectionInfo(); int ipInt = wifiInfo.getIpAddress(); return InetAddress.getByAddress(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array()).getHostAddress(); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/utils/PermissionsUtils.java ================================================ package com.teamopensmartglasses.smartglassesmanager.utils; import android.Manifest; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; //handle all of the permissions stuff public class PermissionsUtils { private AppCompatActivity mCallingActivity; private String TAG; public PermissionsUtils(AppCompatActivity callingActivity, String tag){ mCallingActivity = callingActivity; TAG = tag; } //permissions //file storage and SMS permissions public final String[] EXTERNAL_PERMS = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}; public final int EXTERNAL_REQUEST = 138; //location permissions //private int LOCATION_PERMISSION_CODE = 1; private int REQUEST_PERMISSION_CODE = 1; private int BACKGROUND_LOCATION_PERMISSION_CODE = 2; //handle permissions public boolean requestFilesPermission() { boolean isPermissionOn = true; final int version = Build.VERSION.SDK_INT; if (version >= 23) { if (!canAccessExternalSd()) { isPermissionOn = false; mCallingActivity.requestPermissions(EXTERNAL_PERMS, EXTERNAL_REQUEST); } } return isPermissionOn; } public boolean canAccessExternalSd() { return (hasPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)); } private boolean hasPermission(String perm) { return (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(mCallingActivity, perm)); } public void checkPermission() { if (ContextCompat.checkSelfPermission(mCallingActivity, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { // Fine Location permission is granted // Check if current android version >= 11, if >= 11 check for Background Location permission if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (ContextCompat.checkSelfPermission(mCallingActivity, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED) { // Background Location Permission is granted so do your work here //now, get file permissions requestFilesPermission(); } else { // Ask for Background Location Permission askPermissionForBackgroundUsage(); } } } else { // Fine Location Permission is not granted so ask for permission askForLocationPermission(); } } private void askForLocationPermission() { Log.d(TAG, "run askForLocationPermission"); if (ActivityCompat.shouldShowRequestPermissionRationale(mCallingActivity, Manifest.permission.ACCESS_FINE_LOCATION)) { new AlertDialog.Builder(mCallingActivity) .setTitle("Permission Needed!") .setMessage("Location Permission Needed!") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCompat.requestPermissions(mCallingActivity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.BLUETOOTH_CONNECT,Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.RECORD_AUDIO}, REQUEST_PERMISSION_CODE); } }) .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Permission is denied by the user } }) .create().show(); } else { ActivityCompat.requestPermissions(mCallingActivity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.BLUETOOTH_CONNECT,Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.RECORD_AUDIO}, REQUEST_PERMISSION_CODE); } } private void askPermissionForBackgroundUsage() { Log.d(TAG, "run askPermissionForBackgroundUsage"); if (ActivityCompat.shouldShowRequestPermissionRationale(mCallingActivity, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { new AlertDialog.Builder(mCallingActivity) .setTitle("Location permissions needed.") .setMessage("To tag memories and data based on where you were, the WIS app needs to collect location data. Many features won't work without location permissions. To allow, tap \"Allow all the time\" on the next screen.") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCompat.requestPermissions(mCallingActivity, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, BACKGROUND_LOCATION_PERMISSION_CODE); } }) .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // User declined for Background Location Permission. } }) .create().show(); } else { ActivityCompat.requestPermissions(mCallingActivity, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, BACKGROUND_LOCATION_PERMISSION_CODE); } } public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_PERMISSION_CODE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // User granted location permission // Now check if android version >= 11, if >= 11 check for Background Location Permission if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (ContextCompat.checkSelfPermission(mCallingActivity, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED) { // Background Location Permission is granted so do your work here //now, get file permissions requestFilesPermission(); } else { // Ask for Background Location Permission askPermissionForBackgroundUsage(); } } } else { // User denied location permission } } else if (requestCode == BACKGROUND_LOCATION_PERMISSION_CODE) { //now, get file permissions requestFilesPermission(); if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { } } } public void getSomePermissions(){ ActivityCompat.requestPermissions(mCallingActivity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.RECORD_AUDIO}, REQUEST_PERMISSION_CODE); } } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/asr/cloud_speech_session_params.proto ================================================ syntax = "proto2"; package asr; import "com/google/audio/asr/cloud_speech_stream_observer_params.proto"; import "com/google/audio/codec_and_bitrate.proto"; option java_package = "com.google.audio.asr"; option java_outer_classname = "CloudSpeechSessionParamsProto"; option java_multiple_files = true; message CloudSpeechSessionParams { // Parameters for the observer, who converts the speech protos into // TranscriptionResult. optional CloudSpeechStreamObserverParams observer_params = 1; // Allows profanity to be filtered by the Cloud Speech API. optional bool filter_profanity = 2; // Details about how the audio signal should be compressed prior to sending it // to the server. message EncoderParams { // If the encoder isn't supported, uncompressed audio will be used. When // this is false, other EncoderParams fields are ignored. optional bool enable_encoder = 1 [default = false]; optional audio.CodecAndBitrate codec = 2 [default = OGG_OPUS_BITRATE_32KBPS]; // Uses variable bitrate encoding, if available. Currently this is available // for OggOpus only. optional bool allow_vbr = 3 [default = true]; } optional EncoderParams encoder_params = 3; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/asr/cloud_speech_stream_observer_params.proto ================================================ syntax = "proto2"; package asr; option java_package = "com.google.audio.asr"; option java_outer_classname = "CloudSpeechSessionObserverParamsProto"; option java_multiple_files = true; message CloudSpeechStreamObserverParams { // When true, hypotheses are held a bit longer until they likely will not // change again before being displayed. optional bool reject_unstable_hypotheses = 1; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/asr/speech_recognition_model_options.proto ================================================ syntax = "proto2"; package asr; option java_package = "com.google.audio.asr"; option java_outer_classname = "SpeechRecognitionModelOptionsProto"; option java_multiple_files = true; message SpeechRecognitionModelOptions { optional string locale = 1; // Required. // Select which model to use. Not all models are necessarily available for all // recognition systems or locales. It is up to the individual session to warn // the user about availability enum SpecificModel { DICTATION_DEFAULT = 0; VIDEO = 1; } optional SpecificModel model = 2 [default = DICTATION_DEFAULT]; // Words to be passed to the speech recognizer as bias. It is up to each // implementation to decide whether these will be used or not. repeated string bias_words = 3; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/asr/transcription_result.proto ================================================ syntax = "proto2"; package asr; import "com/google/audio/speaker_id_info.proto"; import "google/protobuf/timestamp.proto"; option java_package = "com.google.audio.asr"; option java_outer_classname = "TranscriptionResultProto"; option java_multiple_files = true; message TranscriptionResult { // An utterance level copy of the text. optional string text = 1; // Confidence for the whole utterance [0, 1]. optional float confidence = 2; // The epoch time at which the utterance was started. optional google.protobuf.Timestamp start_timestamp = 7; // The epoch time at which the utterance was completed. optional google.protobuf.Timestamp end_timestamp = 3; // The identity of the speaker. optional audio.SpeakerIdInfo speaker_info = 5; // Fine-grain information about each word. // NOTE: the TranscriptResultFormatter may colorize the coarse-grain // transcript by the corresponding word information such as confidence and // speaker_id if fine-grain word_level_detail is not empty. message Word { optional string text = 1; // Confidence for just this word [0, 1]. optional float confidence = 2; // An integer tag for the identity of the active speaker. optional audio.SpeakerIdInfo speaker_info = 3; // The time at which the word was started. optional google.protobuf.Timestamp start_timestamp = 4; // The time at which the word was completed. optional google.protobuf.Timestamp end_timestamp = 5; } // Word-level detail. // NOTE: Some recognizers (namely the CloudSpeech API) do not give fine-grain // information until results are finalized. repeated Word word_level_detail = 4; // The language code in this result. // See https://cloud.google.com/speech-to-text/docs/languages for more // details. // For example, // English (United States) : en-US // Chinese, Mandarin (Traditional, Taiwan) : cmn-Hant-TW optional string language_code = 6; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/asr/transcription_result_formatter_options.proto ================================================ syntax = "proto2"; package asr; import "google/protobuf/duration.proto"; option java_package = "com.google.audio.asr"; option java_outer_classname = "TranscriptionResultFormatterOptionsProto"; option java_multiple_files = true; message TranscriptionResultFormatterOptions { // Color selection for the text (does not change background). Dark colors for // a black-on-white theme. Bright colors for a white-on-black theme. enum TextColormap { UNSPECIFIED_THEME = 0; LIGHT_THEME = 1; DARK_THEME = 2; } // Details on the manner in which the transcript will be colored. enum TranscriptColoringStyle { UNSPECIFIED_COLORING_STYLE = 0; // Will do NO_COLORING. NO_COLORING = 1; COLOR_BY_UTTERANCE_LEVEL_CONFIDENCE = 2; COLOR_BY_WORD_LEVEL_CONFIDENCE = 3; COLOR_BY_SPEAKER_ID = 4; } enum SpeakerIndicationStyle { UNSPECIFIED_SPEAKER_INDICATION_STYLE = 0; NO_SPEAKER_INDICATION = 1; SHOW_SPEAKER_NUMBER = 2; } // Silences longer than this will cause a space to be inserted. optional google.protobuf.Duration extended_silence_duration_for_line_breaks = 1; // Number of '\n' characters to add in the event of extended silence. // 1 moves to the next line, 2 leaves a blank space in between two lines, // and so on... optional int32 num_extended_silence_line_breaks = 2 [default = 0]; // Number of '\n' characters to add in the event of language switch. // 1 moves to the next line, 2 leaves a blank space in between two lines, and // so on... optional int32 num_language_switch_line_breaks = 3 [default = 1]; // Put current hypotheses in italics. optional bool italicize_current_hypothesis = 4 [default = false]; // If true, use a yellow->blue colormap to indicate confidence. optional TranscriptColoringStyle transcript_coloring_style = 5 [default = NO_COLORING]; // The color theme used for the text. optional TextColormap text_colormap = 6 [default = DARK_THEME]; // A label that indicates which speaker is active. optional SpeakerIndicationStyle speaker_indication_style = 7 [default = NO_SPEAKER_INDICATION]; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/codec_and_bitrate.proto ================================================ syntax = "proto2"; package audio; option java_package = "com.google.audio"; option java_multiple_files = true; // In Java, (and excluding the FLAC entry) the bitrate in kilobits per second // can be obtained using CodecAndBitrate's getNumber() function. enum CodecAndBitrate { UNDEFINED = 0; // Do not pass this to the encoder. AMRWB_BITRATE_6KBPS = 6600; AMRWB_BITRATE_8KBPS = 8850; AMRWB_BITRATE_12KBPS = 12650; AMRWB_BITRATE_14KBPS = 14250; AMRWB_BITRATE_15KBPS = 15850; AMRWB_BITRATE_18KBPS = 18250; AMRWB_BITRATE_19KBPS = 19850; AMRWB_BITRATE_23KBPS = 23050; AMRWB_BITRATE_24KBPS = 23850; // For FLAC, the bitrate isn't specified. FLAC = 1; // Note: Opus isn't actually limited to specific bitrates like AMRWB is. // // Note that because we run the OggOpusEncoder in low-latency mode, the // actual bitrate may be larger than this. See the ogg_opus_encoder lib for // details. // // The effect of this will be larger at low bitrates and low block // sizes. This is the bitrate used to configure the codec. OGG_OPUS_BITRATE_12KBPS = 12000; OGG_OPUS_BITRATE_16KBPS = 16000; OGG_OPUS_BITRATE_24KBPS = 24000; OGG_OPUS_BITRATE_32KBPS = 32000; OGG_OPUS_BITRATE_64KBPS = 64000; OGG_OPUS_BITRATE_96KBPS = 96000; OGG_OPUS_BITRATE_128KBPS = 128000; } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/network_state.proto ================================================ syntax = "proto2"; package audio; option java_package = "com.google.audio"; option java_multiple_files = true; // Contains information about whether we are connected to the network and // whether the connection is using WiFi. message NetworkState { // If the network is connected. optional bool connected = 1; // Required. // Typically, if false, this means using WiFi. optional bool network_metered = 2; // Required. } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/proto/com/google/audio/speaker_id_info.proto ================================================ syntax = "proto2"; package audio; option java_package = "com.google.audio"; option java_multiple_files = true; message SpeakerIdInfo { optional int32 speaker_id = 1; // In the future, we may have things like the name of registered speakers // etc. } ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/outline_device_hub_24.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/outline_groups_24.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/outline_memory_24.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/outline_settings_24.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/pretty_thin_border.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/rounded_corner.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/rounded_corners_bg.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/rounded_shadow_bg.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/sgm_launcher_background.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/textview_background.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/wis_launcher_background.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable/wis_launcher_transparent_background.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/layout/all_transcripts_fragment.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/layout/api_key_message.xml ================================================ ================================================ FILE: SGM_android/SmartGlassesManager/src/main/res/layout/connecting_to_glasses_fragment.xml ================================================