From 28ddb34d0f144dc97ed8900be9f8520d45d0cb7f Mon Sep 17 00:00:00 2001 From: Obscurus Grassator Date: Tue, 27 Feb 2024 23:18:29 +0100 Subject: [PATCH 1/6] Android implementation --- README.md | 30 +++ src/android/CMakeLists.txt | 48 ++++ src/android/OpenWakeWordServiceExample.java | 233 ++++++++++++++++++++ src/main.cpp | 184 +++++++++++++++- src/main.h | 23 ++ 5 files changed, 510 insertions(+), 8 deletions(-) create mode 100644 src/android/CMakeLists.txt create mode 100644 src/android/OpenWakeWordServiceExample.java create mode 100644 src/main.h diff --git a/README.md b/README.md index 22e4d87..41272e6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ C++ version of [openWakeWord](https://github.com/dscripka/openWakeWord). +# Linux + ## Build 1. Download a release of the [onnxruntime](https://github.com/microsoft/onnxruntime) and extract to `lib/` where `` is `uname -m`. @@ -18,3 +20,31 @@ arecord -r 16000 -c 1 -f S16_LE -t raw - | \ ``` You can add multiple `--model ` arguments. See `--help` for more options. + +# Android + +- openWakeWord-cpp path: `android/app/src/main/cpp/openWakeWord-cpp` +- [onnxruntime-android](https://mvnrepository.com/artifact/com.microsoft.onnxruntime/onnxruntime-android) path: `android/app/src/main/cpp/onnxruntime-android` +- models path moved from android/app/src/main/cpp/openWakeWord-cpp/models to: `android/app/src/main/assets/models` + +android/app/build.gradle: +```gradle +android { + defaultConfig { + minSdkVersion 27 + ndk { + ldLibs "log" + } + } + externalNativeBuild { + cmake { + path "src/main/cpp/openWakeWord-cpp/src/android/CMakeLists.txt" + } + } +``` + +Example of Android service is in `openWakeWord-cpp/src/android/OpenWakeWordServiceExample.java`. C++ part is defaulted end after waking, and started after manualy calling the service (intend) again. +- Extras with `end` property end process. +- Extras with `keyword` start service and set wake model path. Optional is `sensitivity` as string value; +- Don't forget to create first `NotificationChannel` in MainActivity. +- Android destroy service automaticly after same time, that's why you must set `Worker`, which will call this service each 16 minutes. diff --git a/src/android/CMakeLists.txt b/src/android/CMakeLists.txt new file mode 100644 index 0000000..3fb5213 --- /dev/null +++ b/src/android/CMakeLists.txt @@ -0,0 +1,48 @@ +# https://mvnrepository.com/artifact/com.microsoft.onnxruntime/onnxruntime-android/1.16.3 + +cmake_minimum_required(VERSION 3.13) + +project(openWakeWord C CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +string(APPEND CMAKE_CXX_FLAGS " -Wall -Wextra -Wl,-rpath,'$ORIGIN'") +string(APPEND CMAKE_C_FLAGS " -Wall -Wextra") + + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) + +find_package(Threads REQUIRED) + + +add_library(onnxruntime SHARED IMPORTED) +set_target_properties( + onnxruntime + PROPERTIES + IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/../../../onnxruntime-android/jni/${ANDROID_ABI}/libonnxruntime.so + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/../../../onnxruntime-android/headers +) + + +find_library(log-lib log) +find_library(android-lib android) + + +add_library( + openWakeWord + SHARED + ${CMAKE_CURRENT_LIST_DIR}/../main.cpp +) + +target_link_libraries( + openWakeWord + PRIVATE + Threads::Threads + onnxruntime + aaudio + log + ${log-lib} + ${android-lib} +) diff --git a/src/android/OpenWakeWordServiceExample.java b/src/android/OpenWakeWordServiceExample.java new file mode 100644 index 0000000..b3ac31e --- /dev/null +++ b/src/android/OpenWakeWordServiceExample.java @@ -0,0 +1,233 @@ +package com.example; + +import android.app.Service; +import android.media.AudioManager; +import android.media.AudioDeviceInfo; +import android.content.Context; +import android.content.Intent; +import android.content.ComponentName; +import android.content.res.AssetManager; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Build; +import android.util.Log; +import android.app.Notification; +import android.content.pm.ServiceInfo; + +import androidx.core.app.NotificationCompat; +import androidx.annotation.Nullable; +import androidx.work.WorkManager; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.lang.Thread; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.InputStream; +import java.io.FileReader; +import java.io.FileInputStream; +import java.io.OutputStreamWriter; + +class OpenWakeWordOptionsExample { + public String model = null; + public String threshold = null; + public String trigger_level = null; + public String refractory = null; + public String step_frames = null; + public String melspectrogram_model = null; + public String embedding_model = null; + public String debug = null; + public boolean end_after_activation = false; +} + +// backgound service example: https://gist.github.com/varunon9/f2beec0a743c96708eb0ef971a9ff9cd?permalink_comment_id=3831303 +// learning wake word: https://colab.research.google.com/drive/1q1oe2zOyZp7UsB3jJiQ1IFn8z5YfjwEb?usp=sharing + +public class OpenWakeWordServiceExample extends Service { + static { System.loadLibrary("openWakeWord"); } + + private static AssetManager mgr; + + private static Boolean isRunning = false; + private static Boolean ending = false; + private static boolean cppIsRunning = false; + + private static int deviceId = 0; + private static boolean endAfterActivation = true; + private static OpenWakeWordOptionsExample opts = new OpenWakeWordOptionsExample(); + private static String fifoOutFileName; + private static String fifoInFileName; + + public static String workerID = "abc"; + + public native void openWakeWord( + AssetManager mgr, + OpenWakeWordOptionsExample opts, + int deviceId, + String fifoInFileName, + String fifoOutFileName + ); + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + Bundle extras = intent.getExtras(); + if (extras == null) + return Service.START_REDELIVER_INTENT; + + if (extras.getString("end") != null) { + ending = true; + WorkManager.getInstance(this).cancelUniqueWork(workerID); + stopSelf(); + return Service.START_REDELIVER_INTENT; + } + + if (isRunning.equals(true)) { + openWakeWordStart(Integer.valueOf(extras.getString("delayMS", "0"))); + return Service.START_REDELIVER_INTENT; + } + + if (extras.getString("keyword") != null) { + isRunning = true; + + workerID = extras.getString("workerID"); + + String keyword = extras.getString("keyword", "models/alexa_v0.1.onnx"); + String sensitivity = extras.getString("sensitivity", "0.5"); + + File dir = getFilesDir(); + if(!dir.exists()) dir.mkdir(); + fifoOutFileName = getFilesDir() + "/fifoOut"; + fifoInFileName = getFilesDir() + "/fifoIn"; + + Log.d("~= OpenWakeWordService", "onStartCommand - keyword: " + keyword + ", sensitivity: " + sensitivity); + + // TODO: Don't forget to create first NotificationChannel in MainActivity ! + NotificationCompat.Builder notification = new NotificationCompat.Builder(this, workerID) + .setAutoCancel(false) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentTitle("JJAssistant") + .setContentText("JJAssistant Vás počúva na pozadí") + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + int type = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + type = ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; + } + startForeground(99, notification.build(), type); + + new File(fifoOutFileName).delete(); + + // stdout reader and callbeck + new Thread(new Runnable() { + @Override + public void run() { + BufferedReader buffer = null; + try { + while (true) { + try { + Thread.sleep(200); + buffer = new BufferedReader(new InputStreamReader(new FileInputStream(fifoOutFileName))); + break; + } catch (Exception ee) {} + } + + while (true) { + String line = buffer.readLine(); + + if (line == null) Thread.sleep(200); + else { + String name = keyword.substring(7, keyword.length() -5); + + if (line.length() >= name.length() && name.equals(line.substring(1, name.length()+1))) + callback(line); + else if (line.length() >= 7 && "[ERROR]".equals(line.substring(0, 7))) + callback(line, true); + else + Log.d("~= OpenWakeWordService", "stdOut: " + line); + } + } + } catch (Exception e) { + Log.e("~= OpenWakeWordService", "stream output error: " + e.toString()); + try { + if (buffer != null) buffer.close(); + } catch (Exception ee) {} + } + } + }).start(); + + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); + for (AudioDeviceInfo device : devices) { + if (AudioDeviceInfo.TYPE_BUILTIN_MIC == device.getType()) { + deviceId = device.getId(); + break; + } + } + + opts.threshold = sensitivity; + opts.model = keyword; + opts.end_after_activation = endAfterActivation; + opts.trigger_level = "1"; + + openWakeWordStart(); + } + + // by returning this we make sure the service is restarted if the system kills the service + return Service.START_STICKY; + } + + public void openWakeWordStart() { openWakeWordStart(0); } + public void openWakeWordStart(int delayMS) { + mgr = getResources().getAssets(); + + if (cppIsRunning) return; + else cppIsRunning = true; + + new Thread(new Runnable() { + @Override + public void run() { + try { + if (delayMS > 0) Thread.sleep(delayMS); // If is needed time to mic audio input deallocation + openWakeWord(mgr, opts, deviceId, fifoInFileName, fifoOutFileName); + Log.d("~= OpenWakeWordService", "openWakeWord END"); + cppIsRunning = false; + } catch (Exception e) { + Log.e("~= OpenWakeWordService", "c++ error: " + e.toString()); + callback(e.toString(), true); + } + } + }).start(); + } + + public void callback(String message) { callback(message, false); } + public void callback(String message, Boolean error) { + if (error == false) + Log.d("~= OpenWakeWordService", "result: " + result); + else Log.e("~= OpenWakeWordService", "error: " + result); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { return null; } + + @Override + public void onDestroy() { + Log.d("~= OpenWakeWordService", "onDestroy"); + + isRunning = false; + + stopForeground(true); + + // Android destroy service automaticly after same time. + // Android not need call this onDestroy(), that's why you must set worker, which will call this service each 16 minutes. + ... TODO: there call this service again + + super.onDestroy(); + } +} diff --git a/src/main.cpp b/src/main.cpp index 8691e90..aa3d9f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,151 @@ #include +#ifdef __ANDROID__ + + #include + #include + #define LOG_TAG "c++" // the tag to be shown in logcat + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include "main.h" + + FILE *stdin; + std::ofstream fifoOutOfstream; + int fifoIn; + int fifoOut; + AAssetManager *mgr; + AAudioStream *stream; + bool end_after_activation = false; + + aaudio_data_callback_result_t aaudioMicCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t numFrames + ) { + int16_t *samples = static_cast(audioData); + write(fifoIn, samples, numFrames * sizeof(int16_t)); + return AAUDIO_CALLBACK_RESULT_CONTINUE; + } + + off_t getAssset(char** bits, const char* filename) { + AAsset *asset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN); + off_t size = AAsset_getLength(asset); + __android_log_print(ANDROID_LOG_DEBUG, "~= c++", "size of app/src/main/assets/%s: %ld", filename, size); + *bits = new char[size]; + AAsset_read(asset, *bits, size); + AAsset_close(asset); + return size; + } + + void end() { + AAudioStream_close(stream); + close(fifoIn); + fclose(stdin); + fifoOutOfstream.close(); + } + + extern "C" + JNIEXPORT jint JNICALL + Java_com_jjassistant_OpenWakeWordService_openWakeWord( + JNIEnv *env, jobject instance, jobject assetManager, + jobject options, jint deviceId, jstring fifoInFileName, jstring fifoOutFileName + ) { + mgr = AAssetManager_fromJava(env, assetManager); + + char *fifoInFileName2 = (char*)env->GetStringUTFChars(fifoInFileName, 0); + char *fifoOutFileName2 = (char*)env->GetStringUTFChars(fifoOutFileName, 0); + int deviceId2 = (int)deviceId; + + jclass optionsClass = env->GetObjectClass(options); + + jstring modelObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "model", "Ljava/lang/String;")); + jstring thresholdObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "threshold", "Ljava/lang/String;")); + jstring trigger_levelObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "trigger_level", "Ljava/lang/String;")); + jstring refractoryObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "refractory", "Ljava/lang/String;")); + jstring step_framesObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "step_frames", "Ljava/lang/String;")); + jstring melspectrogram_modelObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "melspectrogram_model", "Ljava/lang/String;")); + jstring embedding_modelObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "embedding_model", "Ljava/lang/String;")); + jstring debugObj = (jstring)env->GetObjectField(options, env->GetFieldID(optionsClass, "debug", "Ljava/lang/String;")); + bool end_after_activationObj = env->GetBooleanField(options, env->GetFieldID(optionsClass, "end_after_activation", "Z")); + char *model = (modelObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(modelObj, 0); + char *threshold = (thresholdObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(thresholdObj, 0); + char *trigger_level = (trigger_levelObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(trigger_levelObj, 0); + char *refractory = (refractoryObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(refractoryObj, 0); + char *step_frames = (step_framesObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(step_framesObj, 0); + char *melspectrogram_model = (melspectrogram_modelObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(melspectrogram_modelObj, 0); + char *embedding_model = (embedding_modelObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(embedding_modelObj, 0); + char *debug = (debugObj == NULL) ? (char*)"-" : (char*)env->GetStringUTFChars(debugObj, 0); + end_after_activation = (end_after_activationObj != true) ? false : true; + + AAudioStreamBuilder *builder; + aaudio_result_t result = AAudio_createStreamBuilder(&builder); + + AAudioStreamBuilder_setDeviceId(builder, deviceId2); + AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT); + AAudioStreamBuilder_setSampleRate(builder, 16000); + AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED); + AAudioStreamBuilder_setChannelCount(builder, 1); + AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16); + AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_POWER_SAVING); // AAUDIO_PERFORMANCE_MODE_LOW_LATENCY + AAudioStreamBuilder_setDataCallback(builder, aaudioMicCallback, nullptr); + // AAudioStreamBuilder_setBufferCapacityInFrames(builder, sizeof(int16_t)); + // AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames); + + AAudioStreamBuilder_openStream(builder, &stream); + AAudioStreamBuilder_delete(builder); + + // unlink(fifoOutFileName2); // remove in java + fifoOutOfstream.open(fifoOutFileName2, std::ofstream::out | std::ofstream::app); + std::cerr.rdbuf(fifoOutOfstream.rdbuf()); + std::cout.rdbuf(fifoOutOfstream.rdbuf()); + + unlink(fifoInFileName2); + int fifoInCode = mkfifo(fifoInFileName2, 0666); + fifoIn = open(fifoInFileName2, O_RDWR); // O_RDONLY // O_RDWR | O_NONBLOCK + stdin = fopen(fifoInFileName2, "rb"); + if (fifoInCode == -1 || fifoIn == -1) { + std::cerr << "[ERROR] fifoIn open error: " << strerror(errno) << std::endl; + return -1; + } + + AAudioStream_requestStart(stream); + + char *argv[] = {(char*)"/", + (strcmp(model , "-") == 0) ? (char*)"-" : (char*)"--model", model, + (strcmp(threshold , "-") == 0) ? (char*)"-" : (char*)"--threshold", threshold, + (strcmp(trigger_level , "-") == 0) ? (char*)"-" : (char*)"--trigger-level", trigger_level, + (strcmp(refractory , "-") == 0) ? (char*)"-" : (char*)"--refractory", refractory, + (strcmp(step_frames , "-") == 0) ? (char*)"-" : (char*)"--step-frames", step_frames, + (strcmp(melspectrogram_model , "-") == 0) ? (char*)"-" : (char*)"--melspectrogram-model", melspectrogram_model, + (strcmp(embedding_model , "-") == 0) ? (char*)"-" : (char*)"--embedding-model", embedding_model, + (strcmp(debug , "-") == 0) ? (char*)"-" : (char*)"--debug" + }; + + main(16, argv); + + if (modelObj != NULL) env->ReleaseStringUTFChars(modelObj, model); + if (thresholdObj != NULL) env->ReleaseStringUTFChars(thresholdObj, threshold); + if (trigger_levelObj != NULL) env->ReleaseStringUTFChars(trigger_levelObj, trigger_level); + if (refractoryObj != NULL) env->ReleaseStringUTFChars(refractoryObj, refractory); + if (step_framesObj != NULL) env->ReleaseStringUTFChars(step_framesObj, step_frames); + if (melspectrogram_modelObj != NULL) env->ReleaseStringUTFChars(melspectrogram_modelObj, melspectrogram_model); + if (embedding_modelObj != NULL) env->ReleaseStringUTFChars(embedding_modelObj, embedding_model); + if (debugObj != NULL) env->ReleaseStringUTFChars(debugObj, debug); + + std::cerr << "openWakeWord END" << std::endl; + return 0; + } +#endif + using namespace std; using namespace filesystem; @@ -75,8 +220,12 @@ void audioToMels(Settings &settings, State &state, vector &samplesIn, auto memoryInfo = Ort::MemoryInfo::CreateCpu( OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault); - auto melSession = - Ort::Session(state.env, settings.melModelPath.c_str(), settings.options); + #ifdef __ANDROID__ + char *bits; off_t size = getAssset(&bits, settings.melModelPath.c_str()); + auto melSession = Ort::Session(state.env, bits, size, settings.options); + #else + auto melSession = Ort::Session(state.env, settings.melModelPath.c_str(), settings.options); + #endif vector samplesShape{1, (int64_t)settings.frameSize}; @@ -155,8 +304,12 @@ void melsToFeatures(Settings &settings, State &state, vector &melsIn, auto memoryInfo = Ort::MemoryInfo::CreateCpu( OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault); - auto embSession = - Ort::Session(state.env, settings.embModelPath.c_str(), settings.options); + #ifdef __ANDROID__ + char *bits; off_t size = getAssset(&bits, settings.embModelPath.c_str()); + auto embSession = Ort::Session(state.env, bits, size, settings.options); + #else + auto embSession = Ort::Session(state.env, settings.embModelPath.c_str(), settings.options); + #endif vector embShape{1, (int64_t)embWindowSize, (int64_t)numMels, 1}; @@ -239,8 +392,13 @@ void featuresToOutput(Settings &settings, State &state, size_t wwIdx, auto wwModelPath = settings.wwModelPaths[wwIdx]; auto wwName = wwModelPath.stem(); - auto wwSession = - Ort::Session(state.env, wwModelPath.c_str(), settings.options); + + #ifdef __ANDROID__ + char *bits; off_t size = getAssset(&bits, wwModelPath.c_str()); + auto wwSession = Ort::Session(state.env, bits, size, settings.options); + #else + auto wwSession = Ort::Session(state.env, wwModelPath.c_str(), settings.options); + #endif vector wwShape{1, (int64_t)wwFeatures, (int64_t)embFeatures}; @@ -305,6 +463,10 @@ void featuresToOutput(Settings &settings, State &state, size_t wwIdx, } } + if (probability > 0.1) { + cerr << activation + 1 << " >= " << settings.triggerLevel << " (triggerLevel) && " + << probability << " > " << settings.threshold << " (threshold)" << endl; + } if (probability > settings.threshold) { // Activated activation++; @@ -313,6 +475,10 @@ void featuresToOutput(Settings &settings, State &state, size_t wwIdx, { unique_lock lockOutput(state.mutOutput); cout << wwName << endl; + + #ifdef __ANDROID__ + if (end_after_activation == true) end(); + #endif } activation = -settings.refractory; } @@ -338,8 +504,10 @@ void featuresToOutput(Settings &settings, State &state, size_t wwIdx, int main(int argc, char *argv[]) { - // Re-open stdin/stdout in binary mode - freopen(NULL, "rb", stdin); + #ifndef __ANDROID__ + // Re-open stdin/stdout in binary mode + freopen(NULL, "rb", stdin); + #endif Settings settings; diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..144cda1 --- /dev/null +++ b/src/main.h @@ -0,0 +1,23 @@ +#ifndef MAIN_H // To make sure you don't declare the function more than once by including the header multiple times. +#define MAIN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +void ensureArg(int argc, char *argv[], int argi); +void printUsage(char *argv[]); +int main(int argc, char *argv[]); + +#endif \ No newline at end of file From caa2bbad49b5aa2c5dfe08833265fd6cb2ac1671 Mon Sep 17 00:00:00 2001 From: Obscurus Grassator Date: Sat, 2 Mar 2024 17:55:19 +0100 Subject: [PATCH 2/6] add worker example, update end() --- src/android/OpenWakeWordServiceExample.java | 30 +++++++++++---- src/android/OpenWakeWorkWorkerExample.java | 42 +++++++++++++++++++++ src/main.cpp | 6 ++- 3 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 src/android/OpenWakeWorkWorkerExample.java diff --git a/src/android/OpenWakeWordServiceExample.java b/src/android/OpenWakeWordServiceExample.java index b3ac31e..ac5cef2 100644 --- a/src/android/OpenWakeWordServiceExample.java +++ b/src/android/OpenWakeWordServiceExample.java @@ -16,9 +16,12 @@ import androidx.core.app.NotificationCompat; import androidx.annotation.Nullable; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; import java.util.Date; +import java.util.concurrent.TimeUnit; import java.util.HashMap; import java.util.Map; import java.lang.Thread; @@ -74,6 +77,8 @@ public native void openWakeWord( String fifoOutFileName ); + public static native void endOpenWakeWord(); + @Override public int onStartCommand(Intent intent, int flags, int startId) { @@ -83,13 +88,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (extras.getString("end") != null) { ending = true; - WorkManager.getInstance(this).cancelUniqueWork(workerID); stopSelf(); return Service.START_REDELIVER_INTENT; } if (isRunning.equals(true)) { - openWakeWordStart(Integer.valueOf(extras.getString("delayMS", "0"))); + cppStart(Integer.valueOf(extras.getString("delayMS", "0"))); return Service.START_REDELIVER_INTENT; } @@ -108,7 +112,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { Log.d("~= OpenWakeWordService", "onStartCommand - keyword: " + keyword + ", sensitivity: " + sensitivity); - // TODO: Don't forget to create first NotificationChannel in MainActivity ! NotificationCompat.Builder notification = new NotificationCompat.Builder(this, workerID) .setAutoCancel(false) .setSmallIcon(R.drawable.ic_launcher_foreground) @@ -121,6 +124,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { } startForeground(99, notification.build(), type); + WorkManager.getInstance(this).enqueueUniquePeriodicWork( + workerID, + ExistingPeriodicWorkPolicy.KEEP, + new PeriodicWorkRequest.Builder(OpenWakeWorkWorkerExample.class, 16 /* minimal minutes by documentation */, TimeUnit.MINUTES).build() + ); + new File(fifoOutFileName).delete(); // stdout reader and callbeck @@ -175,15 +184,15 @@ else if (line.length() >= 7 && "[ERROR]".equals(line.substring(0, 7))) opts.end_after_activation = endAfterActivation; opts.trigger_level = "1"; - openWakeWordStart(); + cppStart(); } // by returning this we make sure the service is restarted if the system kills the service return Service.START_STICKY; } - public void openWakeWordStart() { openWakeWordStart(0); } - public void openWakeWordStart(int delayMS) { + public void cppStart() { cppStart(0); } + public void cppStart(int delayMS) { mgr = getResources().getAssets(); if (cppIsRunning) return; @@ -220,13 +229,20 @@ public void callback(String message, Boolean error) { public void onDestroy() { Log.d("~= OpenWakeWordService", "onDestroy"); + endOpenWakeWord(); + isRunning = false; + cppIsRunning = false; stopForeground(true); // Android destroy service automaticly after same time. // Android not need call this onDestroy(), that's why you must set worker, which will call this service each 16 minutes. - ... TODO: there call this service again + if (ending == false) { + ... TODO: there call this service again + } else { + try { WorkManager.getInstance(this).cancelUniqueWork(workerID); } catch (Exception e) {} + } super.onDestroy(); } diff --git a/src/android/OpenWakeWorkWorkerExample.java b/src/android/OpenWakeWorkWorkerExample.java new file mode 100644 index 0000000..6ca053f --- /dev/null +++ b/src/android/OpenWakeWorkWorkerExample.java @@ -0,0 +1,42 @@ +package com.jjassistant; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.work.Worker; +import androidx.work.WorkerParameters; +import androidx.annotation.NonNull; +import androidx.work.ListenableWorker.Result; + +public class OpenWakeWorkWorkerExample extends Worker { + private final Context context; + + public OpenWakeWorkWorkerExample(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + this.context = context; + } + + @NonNull + @Override + public Result doWork() { + Log.d("~= OpenWakeWorkWorker", "sending service restart"); + + Intent intent2 = new Intent(OpenWakeWordService.intentFilterBroadcastString); + + intent2.putExtra("requestID", OpenWakeWordService.requestID); + intent2.putExtra("result", "_restart"); + + getApplicationContext().sendBroadcast(intent2); + + return Result.success(); + } + + // @Override + // public void onStopped() { + // Log.d("~= OpenWakeWorkWorker", "stopped"); + // super.onStopped(); + // } +} diff --git a/src/main.cpp b/src/main.cpp index aa3d9f9..d05f9dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,8 +63,10 @@ fifoOutOfstream.close(); } - extern "C" - JNIEXPORT jint JNICALL + extern "C" JNIEXPORT void JNICALL + Java_com_jjassistant_OpenWakeWordService_endOpenWakeWord() { end(); } + + extern "C" JNIEXPORT jint JNICALL Java_com_jjassistant_OpenWakeWordService_openWakeWord( JNIEnv *env, jobject instance, jobject assetManager, jobject options, jint deviceId, jstring fifoInFileName, jstring fifoOutFileName From e9e5f95061c03b52a5bf27ee6711a1a82204a93c Mon Sep 17 00:00:00 2001 From: ObscurusGrassator Date: Tue, 9 Jul 2024 15:40:07 +0200 Subject: [PATCH 3/6] FIX stop() and end() in android service example --- README.md | 9 +- src/android/OpenWakeWordServiceExample.java | 252 ++++++++++++++------ src/android/OpenWakeWorkWorkerExample.java | 4 +- src/main.cpp | 1 + 4 files changed, 185 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 41272e6..56d9872 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,11 @@ android { } ``` -Example of Android service is in `openWakeWord-cpp/src/android/OpenWakeWordServiceExample.java`. C++ part is defaulted end after waking, and started after manualy calling the service (intend) again. -- Extras with `end` property end process. -- Extras with `keyword` start service and set wake model path. Optional is `sensitivity` as string value; +Example of Android service is in `openWakeWord-cpp/src/android/OpenWakeWordServiceExample.java`. There C++ part is defaulted end after waking, and started after manualy calling the service (intend) again. +- Extras with `end` property end service. +- Extras with `stop` property end cpp subprocess. +- Extras with `keyword` property start service, or only cpp subprocess, and set wake word model path. Default is `models/alexa_v0.1.onnx`. + - Optional extras `sensitivity` as string value. Default is `0.5`. + - Optional extras `closeServiceAfterWakeWordActivation` property end android service after waking (cpp subprocess is end by hardcode). Default is `false`. - Don't forget to create first `NotificationChannel` in MainActivity. - Android destroy service automaticly after same time, that's why you must set `Worker`, which will call this service each 16 minutes. diff --git a/src/android/OpenWakeWordServiceExample.java b/src/android/OpenWakeWordServiceExample.java index ac5cef2..4cd9c7d 100644 --- a/src/android/OpenWakeWordServiceExample.java +++ b/src/android/OpenWakeWordServiceExample.java @@ -1,4 +1,4 @@ -package com.example; +package com.jjassistant; import android.app.Service; import android.media.AudioManager; @@ -57,62 +57,86 @@ public class OpenWakeWordServiceExample extends Service { private static AssetManager mgr; - private static Boolean isRunning = false; - private static Boolean ending = false; - private static boolean cppIsRunning = false; - + // 0 - app is starting ... + // * 1 - app is runned // _STARTED + // 2 - app is stopping .. + // 21 - waking or cpp end ... + // * 22 - waking and cpp end // _STOPPED + // 23 - cpp starting .. + // 3 - closing strems (after cpp end) ... + // * 4 - app is dead // _ENDED + private static Number lifeCycle = 4; + private static Boolean endApp = false; + private static Boolean stopApp = false; + + private static boolean closeServiceAfterWakeWordActivation = false; private static int deviceId = 0; - private static boolean endAfterActivation = true; private static OpenWakeWordOptionsExample opts = new OpenWakeWordOptionsExample(); private static String fifoOutFileName; private static String fifoInFileName; + private static WorkManager worker; - public static String workerID = "abc"; - - public native void openWakeWord( - AssetManager mgr, - OpenWakeWordOptionsExample opts, - int deviceId, - String fifoInFileName, - String fifoOutFileName - ); + public static String requestID; + public static String intentFilterBroadcastString; + public static String workerName = "JJPluginWakeWordServiceRestertWorker"; + public native void openWakeWord(AssetManager mgr, OpenWakeWordOptionsExample opts, int deviceId, String fifoInFileName, String fifoOutFileName); public static native void endOpenWakeWord(); @Override public int onStartCommand(Intent intent, int flags, int startId) { + Log.d("~= OpenWakeWordService", "onStartCommand() lifeCycle: " + lifeCycle); Bundle extras = intent.getExtras(); if (extras == null) return Service.START_REDELIVER_INTENT; - if (extras.getString("end") != null) { - ending = true; - stopSelf(); + if (extras.getString("end") != null && lifeCycle.equals(1)) { + lifeCycle = 2; + endApp = true; + endOpenWakeWord(); + return super.onStartCommand(intent, flags, startId); + // return Service.START_REDELIVER_INTENT; + } + else if (extras.getString("end") != null && lifeCycle.equals(22)) { + lifeCycle = 3; + return super.onStartCommand(intent, flags, startId); + } + else if (extras.getString("end") != null && lifeCycle.equals(4)) { + return super.onStartCommand(intent, flags, startId); + } + else if (extras.getString("stop") != null && lifeCycle.equals(1)) { + lifeCycle = 2; + stopApp = true; + endOpenWakeWord(); return Service.START_REDELIVER_INTENT; } - - if (isRunning.equals(true)) { - cppStart(Integer.valueOf(extras.getString("delayMS", "0"))); + else if (lifeCycle.equals(1)) { return Service.START_REDELIVER_INTENT; } + else if (lifeCycle.equals(22)) { + lifeCycle = 23; + stopApp = false; + cppStart(extras, Integer.valueOf(extras.getString("delayMS", "0"))); + return Service.START_REDELIVER_INTENT; + } + else if (lifeCycle.equals(4) && extras.getString("keyword") != null) { + Log.d("~= OpenWakeWordService", "STARTING"); - if (extras.getString("keyword") != null) { - isRunning = true; - - workerID = extras.getString("workerID"); + lifeCycle = 0; + endApp = false; + stopApp = false; - String keyword = extras.getString("keyword", "models/alexa_v0.1.onnx"); - String sensitivity = extras.getString("sensitivity", "0.5"); + closeServiceAfterWakeWordActivation = Boolean.parseBoolean(extras.getString("closeServiceAfterWakeWordActivation", "false")); + requestID = extras.getString("requestID"); + intentFilterBroadcastString = extras.getString("intentFilterBroadcastString"); File dir = getFilesDir(); if(!dir.exists()) dir.mkdir(); fifoOutFileName = getFilesDir() + "/fifoOut"; fifoInFileName = getFilesDir() + "/fifoIn"; - Log.d("~= OpenWakeWordService", "onStartCommand - keyword: " + keyword + ", sensitivity: " + sensitivity); - - NotificationCompat.Builder notification = new NotificationCompat.Builder(this, workerID) + NotificationCompat.Builder notification = new NotificationCompat.Builder(this, intentFilterBroadcastString) .setAutoCancel(false) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("JJAssistant") @@ -124,11 +148,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { } startForeground(99, notification.build(), type); - WorkManager.getInstance(this).enqueueUniquePeriodicWork( - workerID, - ExistingPeriodicWorkPolicy.KEEP, - new PeriodicWorkRequest.Builder(OpenWakeWorkWorkerExample.class, 16 /* minimal minutes by documentation */, TimeUnit.MINUTES).build() - ); + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); + for (AudioDeviceInfo device : devices) { + if (AudioDeviceInfo.TYPE_BUILTIN_MIC == device.getType()) { + deviceId = device.getId(); + break; + } + } new File(fifoOutFileName).delete(); @@ -138,7 +165,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { public void run() { BufferedReader buffer = null; try { - while (true) { + while (!lifeCycle.equals(3)) { try { Thread.sleep(200); buffer = new BufferedReader(new InputStreamReader(new FileInputStream(fifoOutFileName))); @@ -146,21 +173,44 @@ public void run() { } catch (Exception ee) {} } - while (true) { + while (!lifeCycle.equals(3)) { String line = buffer.readLine(); if (line == null) Thread.sleep(200); else { - String name = keyword.substring(7, keyword.length() -5); + String name = opts.model.substring(7, opts.model.length() -5); + + if (line.contains("[LOG] Ready")) { + lifeCycle = 1; + callback("_STARTED"); + } - if (line.length() >= name.length() && name.equals(line.substring(1, name.length()+1))) + if (line.length() >= name.length() && name.equals(line.substring(1, name.length()+1))) { callback(line); + + if (lifeCycle.equals(21)) { + lifeCycle = 22; + callback("_STOPPED"); + + if (endApp || closeServiceAfterWakeWordActivation) { + endApp = true; + lifeCycle = 3; + } + } + else if (lifeCycle.equals(1) || lifeCycle.equals(2)) + lifeCycle = 21; + } else if (line.length() >= 7 && "[ERROR]".equals(line.substring(0, 7))) callback(line, true); else Log.d("~= OpenWakeWordService", "stdOut: " + line); } } + + buffer.close(); + new File(fifoOutFileName).delete(); + + stopSelf(); } catch (Exception e) { Log.e("~= OpenWakeWordService", "stream output error: " + e.toString()); try { @@ -170,45 +220,68 @@ else if (line.length() >= 7 && "[ERROR]".equals(line.substring(0, 7))) } }).start(); - AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); - for (AudioDeviceInfo device : devices) { - if (AudioDeviceInfo.TYPE_BUILTIN_MIC == device.getType()) { - deviceId = device.getId(); - break; - } - } + cppStart(extras, Integer.valueOf(extras.getString("delayMS", "0"))); - opts.threshold = sensitivity; - opts.model = keyword; - opts.end_after_activation = endAfterActivation; - opts.trigger_level = "1"; + // by returning this we make sure the service is restarted if the system kills the service + return Service.START_STICKY; + } + else { + Log.e("~= OpenWakeWordService", "App is in lifeCycle: " + lifeCycle + ", and is not ready to " + + (extras.getString("keyword") != null ? "start" : "") + + (extras.getString("end") != null ? "end" : "") + + (extras.getString("stop") != null ? "stop" : "") + ); + return Service.START_REDELIVER_INTENT; + } + } - cppStart(); + public void cppStart(Bundle extras) { cppStart(extras, 0); } + public void cppStart(Bundle extras, int delayMS) { + if (extras.getString("keyword") != null) { + opts.model = extras.getString("keyword", "models/alexa_v0.1.onnx"); + opts.threshold = extras.getString("sensitivity", "0.5"); + opts.end_after_activation = true; + opts.trigger_level = "1"; } - // by returning this we make sure the service is restarted if the system kills the service - return Service.START_STICKY; - } + Log.d("~= OpenWakeWordService", "openWakeWord cpp and worker STARTING - keyword: " + opts.model + ", sensitivity: " + opts.threshold); - public void cppStart() { cppStart(0); } - public void cppStart(int delayMS) { - mgr = getResources().getAssets(); + worker = WorkManager.getInstance(this); + worker.enqueueUniquePeriodicWork( + workerName, + ExistingPeriodicWorkPolicy.KEEP, + new PeriodicWorkRequest.Builder(OpenWakeWorkWorker.class, 16 /* minimal minutes by documentation */, TimeUnit.MINUTES).build() + ); - if (cppIsRunning) return; - else cppIsRunning = true; + mgr = getResources().getAssets(); new Thread(new Runnable() { @Override public void run() { try { if (delayMS > 0) Thread.sleep(delayMS); // If is needed time to mic audio input deallocation + openWakeWord(mgr, opts, deviceId, fifoInFileName, fifoOutFileName); - Log.d("~= OpenWakeWordService", "openWakeWord END"); - cppIsRunning = false; + + worker.cancelUniqueWork(workerName); + + Log.d("~= OpenWakeWordService", "openWakeWord cpp and worker ENDED"); + + if (stopApp || endApp || lifeCycle.equals(21)) { + lifeCycle = 22; + callback("_STOPPED"); + + if (endApp || closeServiceAfterWakeWordActivation) { + endApp = true; + lifeCycle = 3; + } + } + else if (lifeCycle.equals(1) || lifeCycle.equals(2)) + lifeCycle = 21; } catch (Exception e) { Log.e("~= OpenWakeWordService", "c++ error: " + e.toString()); callback(e.toString(), true); + cppStart(extras); } } }).start(); @@ -216,9 +289,20 @@ public void run() { public void callback(String message) { callback(message, false); } public void callback(String message, Boolean error) { - if (error == false) - Log.d("~= OpenWakeWordService", "result: " + result); - else Log.e("~= OpenWakeWordService", "error: " + result); + try { + Log.d("~= OpenWakeWordService", "callback: " + message); + + Intent intent2 = new Intent(intentFilterBroadcastString); + + intent2.putExtra("requestID", requestID); + intent2.putExtra(error ? "error" : "result", message); + + // message to app: _STARTED / _RESTARTME / _STOPPED / _ENDED / + sendBroadcast(intent2); + } catch (Exception e) { + Log.e("~= OpenWakeWordService", "Resolve intent error: " + e.toString()); + throw new RuntimeException(e); + } } @Nullable @@ -227,23 +311,39 @@ public void callback(String message, Boolean error) { @Override public void onDestroy() { - Log.d("~= OpenWakeWordService", "onDestroy"); - - endOpenWakeWord(); + // Android destroy service automaticly after same time. + // Android not need call this onDestroy(), that's why you must set worker, which will call this service each 16 minutes. + if (endApp.equals(false)) { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1000); - isRunning = false; - cppIsRunning = false; + Intent intent2 = new Intent(intentFilterBroadcastString); - stopForeground(true); + intent2.putExtra("requestID", requestID); + intent2.putExtra("result", "_RESTARTME"); - // Android destroy service automaticly after same time. - // Android not need call this onDestroy(), that's why you must set worker, which will call this service each 16 minutes. - if (ending == false) { - ... TODO: there call this service again + // please restart me + sendBroadcast(intent2); + } catch (Exception e) { + Log.w("~= OpenWakeWordService", "onDestroy() restart error: " + e.toString()); + } + } + }).start(); } else { - try { WorkManager.getInstance(this).cancelUniqueWork(workerID); } catch (Exception e) {} + try { worker.cancelUniqueWork(workerName); } catch (Exception e) {} + Log.d("~= OpenWakeWordService", "worker END"); } + lifeCycle = 4; + callback("_ENDED"); + + stopForeground(true); + super.onDestroy(); + + Log.d("~= OpenWakeWordService", "...DESTROIED"); } } diff --git a/src/android/OpenWakeWorkWorkerExample.java b/src/android/OpenWakeWorkWorkerExample.java index 6ca053f..b143fa7 100644 --- a/src/android/OpenWakeWorkWorkerExample.java +++ b/src/android/OpenWakeWorkWorkerExample.java @@ -24,10 +24,10 @@ public OpenWakeWorkWorkerExample(@NonNull Context context, @NonNull WorkerParame public Result doWork() { Log.d("~= OpenWakeWorkWorker", "sending service restart"); - Intent intent2 = new Intent(OpenWakeWordService.intentFilterBroadcastString); + Intent intent2 = new Intent(OpenWakeWordService.workerName); intent2.putExtra("requestID", OpenWakeWordService.requestID); - intent2.putExtra("result", "_restart"); + intent2.putExtra("result", "_RESTARTME"); getApplicationContext().sendBroadcast(intent2); diff --git a/src/main.cpp b/src/main.cpp index d05f9dd..cc99cba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,6 +57,7 @@ } void end() { + AAudioStream_requestStop(stream); AAudioStream_close(stream); close(fifoIn); fclose(stdin); From 86c099ce87cc3c7e8d6ca9cd7c5038a9ae48b768 Mon Sep 17 00:00:00 2001 From: Obscurus Grassator Date: Thu, 28 Nov 2024 17:27:14 +0100 Subject: [PATCH 4/6] Update CMakeLists.txt --- src/android/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/android/CMakeLists.txt b/src/android/CMakeLists.txt index 3fb5213..468ab40 100644 --- a/src/android/CMakeLists.txt +++ b/src/android/CMakeLists.txt @@ -1,13 +1,13 @@ # https://mvnrepository.com/artifact/com.microsoft.onnxruntime/onnxruntime-android/1.16.3 -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.22.1) -project(openWakeWord C CXX) +project(appmodules C CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -string(APPEND CMAKE_CXX_FLAGS " -Wall -Wextra -Wl,-rpath,'$ORIGIN'") +string(APPEND CMAKE_CXX_FLAGS " -Wall -Wextra") # -Wl,-rpath,'$ORIGIN' string(APPEND CMAKE_C_FLAGS " -Wall -Wextra") From b9bd471769f37f3070c081e044a9191d95fd9ded Mon Sep 17 00:00:00 2001 From: Obscurus Grassator Date: Sat, 28 Dec 2024 02:35:20 +0100 Subject: [PATCH 5/6] Update OpenWakeWordServiceExample.java --- src/android/OpenWakeWordServiceExample.java | 54 +++++++++++++-------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/android/OpenWakeWordServiceExample.java b/src/android/OpenWakeWordServiceExample.java index 4cd9c7d..59241a7 100644 --- a/src/android/OpenWakeWordServiceExample.java +++ b/src/android/OpenWakeWordServiceExample.java @@ -1,18 +1,18 @@ package com.jjassistant; import android.app.Service; +import android.app.Notification; import android.media.AudioManager; import android.media.AudioDeviceInfo; import android.content.Context; import android.content.Intent; import android.content.ComponentName; import android.content.res.AssetManager; +import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.IBinder; import android.os.Build; import android.util.Log; -import android.app.Notification; -import android.content.pm.ServiceInfo; import androidx.core.app.NotificationCompat; import androidx.annotation.Nullable; @@ -75,8 +75,8 @@ public class OpenWakeWordServiceExample extends Service { private static String fifoOutFileName; private static String fifoInFileName; private static WorkManager worker; + private static String old_requestID = ""; - public static String requestID; public static String intentFilterBroadcastString; public static String workerName = "JJPluginWakeWordServiceRestertWorker"; @@ -88,22 +88,32 @@ public int onStartCommand(Intent intent, int flags, int startId) { Log.d("~= OpenWakeWordService", "onStartCommand() lifeCycle: " + lifeCycle); Bundle extras = intent.getExtras(); - if (extras == null) + if (extras == null) return Service.START_REDELIVER_INTENT; + + String requestID = extras.getString("requestID"); + intentFilterBroadcastString = extras.getString("intentFilterBroadcastString", intentFilterBroadcastString); + + if (requestID.equals(old_requestID)) { return Service.START_REDELIVER_INTENT; + } else old_requestID = requestID; if (extras.getString("end") != null && lifeCycle.equals(1)) { lifeCycle = 2; endApp = true; endOpenWakeWord(); - return super.onStartCommand(intent, flags, startId); - // return Service.START_REDELIVER_INTENT; + return Service.START_REDELIVER_INTENT; } else if (extras.getString("end") != null && lifeCycle.equals(22)) { lifeCycle = 3; - return super.onStartCommand(intent, flags, startId); + return Service.START_REDELIVER_INTENT; } else if (extras.getString("end") != null && lifeCycle.equals(4)) { - return super.onStartCommand(intent, flags, startId); + callback("_ENDED"); + return Service.START_REDELIVER_INTENT; + } + else if (extras.getString("stop") != null && lifeCycle.equals(22)) { + callback("_STOPPED"); + return Service.START_REDELIVER_INTENT; } else if (extras.getString("stop") != null && lifeCycle.equals(1)) { lifeCycle = 2; @@ -112,6 +122,7 @@ else if (extras.getString("stop") != null && lifeCycle.equals(1)) { return Service.START_REDELIVER_INTENT; } else if (lifeCycle.equals(1)) { + callback("_STARTED"); return Service.START_REDELIVER_INTENT; } else if (lifeCycle.equals(22)) { @@ -128,14 +139,12 @@ else if (lifeCycle.equals(4) && extras.getString("keyword") != null) { stopApp = false; closeServiceAfterWakeWordActivation = Boolean.parseBoolean(extras.getString("closeServiceAfterWakeWordActivation", "false")); - requestID = extras.getString("requestID"); - intentFilterBroadcastString = extras.getString("intentFilterBroadcastString"); File dir = getFilesDir(); if(!dir.exists()) dir.mkdir(); fifoOutFileName = getFilesDir() + "/fifoOut"; fifoInFileName = getFilesDir() + "/fifoIn"; - + NotificationCompat.Builder notification = new NotificationCompat.Builder(this, intentFilterBroadcastString) .setAutoCancel(false) .setSmallIcon(R.drawable.ic_launcher_foreground) @@ -226,11 +235,11 @@ else if (line.length() >= 7 && "[ERROR]".equals(line.substring(0, 7))) return Service.START_STICKY; } else { - Log.e("~= OpenWakeWordService", "App is in lifeCycle: " + lifeCycle + ", and is not ready to " + String err = "App is in lifeCycle: " + lifeCycle + ", and is not ready to " + (extras.getString("keyword") != null ? "start" : "") + (extras.getString("end") != null ? "end" : "") - + (extras.getString("stop") != null ? "stop" : "") - ); + + (extras.getString("stop") != null ? "stop" : ""); + callback(err, true); return Service.START_REDELIVER_INTENT; } } @@ -279,18 +288,22 @@ public void run() { else if (lifeCycle.equals(1) || lifeCycle.equals(2)) lifeCycle = 21; } catch (Exception e) { - Log.e("~= OpenWakeWordService", "c++ error: " + e.toString()); - callback(e.toString(), true); + callback("c++ error: " + e.toString(), true); cppStart(extras); } } }).start(); } - public void callback(String message) { callback(message, false); } - public void callback(String message, Boolean error) { + public void callback(String message) { callback(message, false, "openWakeWord"); } + public void callback(String message, Boolean error) { callback(message, error, "openWakeWord"); } + public void callback(String message, Boolean error, String requestID) { try { - Log.d("~= OpenWakeWordService", "callback: " + message); + // Thread.sleep(100); + + if (error == true) + Log.e("~= OpenWakeWordService", "callback error: " + message); + else Log.d("~= OpenWakeWordService", "callback result: " + message); Intent intent2 = new Intent(intentFilterBroadcastString); @@ -322,7 +335,7 @@ public void run() { Intent intent2 = new Intent(intentFilterBroadcastString); - intent2.putExtra("requestID", requestID); + intent2.putExtra("requestID", "openWakeWord"); intent2.putExtra("result", "_RESTARTME"); // please restart me @@ -346,4 +359,5 @@ public void run() { Log.d("~= OpenWakeWordService", "...DESTROIED"); } + } From 55a6684081e41773e22cb83b8d5d0d01a80d290f Mon Sep 17 00:00:00 2001 From: Obscurus Grassator Date: Sat, 28 Dec 2024 02:35:55 +0100 Subject: [PATCH 6/6] Update OpenWakeWorkWorkerExample.java --- src/android/OpenWakeWorkWorkerExample.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/OpenWakeWorkWorkerExample.java b/src/android/OpenWakeWorkWorkerExample.java index b143fa7..1f65fb9 100644 --- a/src/android/OpenWakeWorkWorkerExample.java +++ b/src/android/OpenWakeWorkWorkerExample.java @@ -26,7 +26,7 @@ public Result doWork() { Intent intent2 = new Intent(OpenWakeWordService.workerName); - intent2.putExtra("requestID", OpenWakeWordService.requestID); + intent2.putExtra("requestID", "openWakeWord"); intent2.putExtra("result", "_RESTARTME"); getApplicationContext().sendBroadcast(intent2);