diff --git a/README.md b/README.md index 22e4d87..56d9872 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,34 @@ 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`. 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/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..4cd9c7d --- /dev/null +++ b/src/android/OpenWakeWordServiceExample.java @@ -0,0 +1,349 @@ +package com.jjassistant; + +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.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; + +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; + + // 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 OpenWakeWordOptionsExample opts = new OpenWakeWordOptionsExample(); + private static String fifoOutFileName; + private static String fifoInFileName; + private static WorkManager worker; + + 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 && 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; + } + 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"); + + lifeCycle = 0; + endApp = false; + 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) + .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); + + 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(); + + // stdout reader and callbeck + new Thread(new Runnable() { + @Override + public void run() { + BufferedReader buffer = null; + try { + while (!lifeCycle.equals(3)) { + try { + Thread.sleep(200); + buffer = new BufferedReader(new InputStreamReader(new FileInputStream(fifoOutFileName))); + break; + } catch (Exception ee) {} + } + + while (!lifeCycle.equals(3)) { + String line = buffer.readLine(); + + if (line == null) Thread.sleep(200); + else { + 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))) { + 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 { + if (buffer != null) buffer.close(); + } catch (Exception ee) {} + } + } + }).start(); + + cppStart(extras, Integer.valueOf(extras.getString("delayMS", "0"))); + + // 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; + } + } + + 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"; + } + + Log.d("~= OpenWakeWordService", "openWakeWord cpp and worker STARTING - keyword: " + opts.model + ", sensitivity: " + opts.threshold); + + worker = WorkManager.getInstance(this); + worker.enqueueUniquePeriodicWork( + workerName, + ExistingPeriodicWorkPolicy.KEEP, + new PeriodicWorkRequest.Builder(OpenWakeWorkWorker.class, 16 /* minimal minutes by documentation */, TimeUnit.MINUTES).build() + ); + + 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); + + 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(); + } + + public void callback(String message) { callback(message, false); } + public void callback(String message, Boolean error) { + 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 + @Override + public IBinder onBind(Intent intent) { return null; } + + @Override + public void onDestroy() { + // 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); + + Intent intent2 = new Intent(intentFilterBroadcastString); + + intent2.putExtra("requestID", requestID); + intent2.putExtra("result", "_RESTARTME"); + + // please restart me + sendBroadcast(intent2); + } catch (Exception e) { + Log.w("~= OpenWakeWordService", "onDestroy() restart error: " + e.toString()); + } + } + }).start(); + } else { + 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 new file mode 100644 index 0000000..b143fa7 --- /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.workerName); + + intent2.putExtra("requestID", OpenWakeWordService.requestID); + intent2.putExtra("result", "_RESTARTME"); + + 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 8691e90..cc99cba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,154 @@ #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_requestStop(stream); + AAudioStream_close(stream); + close(fifoIn); + fclose(stdin); + fifoOutOfstream.close(); + } + + 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 + ) { + 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 +223,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 +307,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 +395,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 +466,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 +478,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 +507,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