/* ** ** Copyright 2014, The Android Open Source Project ** ** 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 ** ** http://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. */ //#define LOG_NDEBUG 0 #define LOG_TAG "SoundTrigger-JNI" #include #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include #include #include #include #include #include #include using namespace android; static jclass gArrayListClass; static struct { jmethodID add; } gArrayListMethods; static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger"; static jclass gSoundTriggerClass; static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule"; static jclass gModuleClass; static struct { jfieldID mNativeContext; jfieldID mId; } gModuleFields; static jmethodID gPostEventFromNative; static const char* const kModulePropertiesClassPathName = "android/hardware/soundtrigger/SoundTrigger$ModuleProperties"; static jclass gModulePropertiesClass; static jmethodID gModulePropertiesCstor; static const char* const kSoundModelClassPathName = "android/hardware/soundtrigger/SoundTrigger$SoundModel"; static jclass gSoundModelClass; static struct { jfieldID data; } gSoundModelFields; static const char* const kKeyPhraseClassPathName = "android/hardware/soundtrigger/SoundTrigger$KeyPhrase"; static jclass gKeyPhraseClass; static struct { jfieldID recognitionModes; jfieldID locale; jfieldID text; jfieldID numUsers; } gKeyPhraseFields; static const char* const kKeyPhraseSoundModelClassPathName = "android/hardware/soundtrigger/SoundTrigger$KeyPhraseSoundModel"; static jclass gKeyPhraseSoundModelClass; static struct { jfieldID keyPhrases; } gKeyPhraseSoundModelFields; static const char* const kRecognitionEventClassPathName = "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent"; static jclass gRecognitionEventClass; static jmethodID gRecognitionEventCstor; static const char* const kKeyPhraseRecognitionEventClassPathName = "android/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionEvent"; static jclass gKeyPhraseRecognitionEventClass; static jmethodID gKeyPhraseRecognitionEventCstor; static const char* const kKeyPhraseRecognitionExtraClassPathName = "android/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionExtra"; static jclass gKeyPhraseRecognitionExtraClass; static jmethodID gKeyPhraseRecognitionExtraCstor; static Mutex gLock; enum { SOUNDTRIGGER_STATUS_OK = 0, SOUNDTRIGGER_STATUS_ERROR = INT_MIN, SOUNDTRIGGER_PERMISSION_DENIED = -1, SOUNDTRIGGER_STATUS_NO_INIT = -19, SOUNDTRIGGER_STATUS_BAD_VALUE = -22, SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32, SOUNDTRIGGER_INVALID_OPERATION = -38, }; enum { SOUNDTRIGGER_EVENT_RECOGNITION = 1, SOUNDTRIGGER_EVENT_SERVICE_DIED = 2, }; // ---------------------------------------------------------------------------- // ref-counted object for callbacks class JNISoundTriggerCallback: public SoundTriggerCallback { public: JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz); ~JNISoundTriggerCallback(); virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event); virtual void onServiceDied(); private: jclass mClass; // Reference to SoundTrigger class jobject mObject; // Weak ref to SoundTrigger Java object to call on }; JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz) { // Hold onto the SoundTriggerModule class for use in calling the static method // that posts events to the application thread. jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { ALOGE("Can't find class %s", kModuleClassPathName); return; } mClass = (jclass)env->NewGlobalRef(clazz); // We use a weak reference so the SoundTriggerModule object can be garbage collected. // The reference is only used as a proxy for callbacks. mObject = env->NewGlobalRef(weak_thiz); } JNISoundTriggerCallback::~JNISoundTriggerCallback() { // remove global references JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mObject); env->DeleteGlobalRef(mClass); } void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jobject jEvent; jbyteArray jData = NULL; if (event->data_size) { jData = env->NewByteArray(event->data_size); jbyte *nData = env->GetByteArrayElements(jData, NULL); memcpy(nData, (char *)event + event->data_offset, event->data_size); env->ReleaseByteArrayElements(jData, nData, 0); } if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) { struct sound_trigger_phrase_recognition_event *phraseEvent = (struct sound_trigger_phrase_recognition_event *)event; jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases, gKeyPhraseRecognitionExtraClass, NULL); if (jExtras == NULL) { return; } for (size_t i = 0; i < phraseEvent->num_phrases; i++) { jintArray jConfidenceLevels = env->NewIntArray(phraseEvent->phrase_extras[i].num_users); if (jConfidenceLevels == NULL) { return; } jint *nConfidenceLevels = env->GetIntArrayElements(jConfidenceLevels, NULL); memcpy(nConfidenceLevels, phraseEvent->phrase_extras[i].confidence_levels, phraseEvent->phrase_extras[i].num_users * sizeof(int)); env->ReleaseIntArrayElements(jConfidenceLevels, nConfidenceLevels, 0); jobject jNewExtra = env->NewObject(gKeyPhraseRecognitionExtraClass, gKeyPhraseRecognitionExtraCstor, jConfidenceLevels, phraseEvent->phrase_extras[i].recognition_modes); if (jNewExtra == NULL) { return; } env->SetObjectArrayElement(jExtras, i, jNewExtra); } jEvent = env->NewObject(gKeyPhraseRecognitionEventClass, gKeyPhraseRecognitionEventCstor, event->status, event->model, event->capture_available, event->capture_session, event->capture_delay_ms, jData, phraseEvent->key_phrase_in_capture, jExtras); } else { jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor, event->status, event->model, event->capture_available, event->capture_session, event->capture_delay_ms, jData); } env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject, SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent); if (env->ExceptionCheck()) { ALOGW("An exception occurred while notifying an event."); env->ExceptionClear(); } } void JNISoundTriggerCallback::onServiceDied() { JNIEnv *env = AndroidRuntime::getJNIEnv(); env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject, SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL); if (env->ExceptionCheck()) { ALOGW("An exception occurred while notifying an event."); env->ExceptionClear(); } } // ---------------------------------------------------------------------------- static sp getSoundTrigger(JNIEnv* env, jobject thiz) { Mutex::Autolock l(gLock); SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz, gModuleFields.mNativeContext); return sp(st); } static sp setSoundTrigger(JNIEnv* env, jobject thiz, const sp& module) { Mutex::Autolock l(gLock); sp old = (SoundTrigger*)env->GetLongField(thiz, gModuleFields.mNativeContext); if (module.get()) { module->incStrong((void*)setSoundTrigger); } if (old != 0) { old->decStrong((void*)setSoundTrigger); } env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get()); return old; } static jint android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz, jobject jModules) { ALOGV("listModules"); if (jModules == NULL) { ALOGE("listModules NULL AudioPatch ArrayList"); return SOUNDTRIGGER_STATUS_BAD_VALUE; } if (!env->IsInstanceOf(jModules, gArrayListClass)) { ALOGE("listModules not an arraylist"); return SOUNDTRIGGER_STATUS_BAD_VALUE; } unsigned int numModules = 0; struct sound_trigger_module_descriptor *nModules = NULL; status_t status = SoundTrigger::listModules(nModules, &numModules); if (status != NO_ERROR || numModules == 0) { return (jint)status; } nModules = (struct sound_trigger_module_descriptor *) calloc(numModules, sizeof(struct sound_trigger_module_descriptor)); status = SoundTrigger::listModules(nModules, &numModules); ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules); if (status != NO_ERROR) { numModules = 0; } for (size_t i = 0; i < numModules; i++) { char str[SOUND_TRIGGER_MAX_STRING_LEN]; jstring implementor = env->NewStringUTF(nModules[i].properties.implementor); jstring description = env->NewStringUTF(nModules[i].properties.description); SoundTrigger::guidToString(&nModules[i].properties.uuid, str, SOUND_TRIGGER_MAX_STRING_LEN); jstring uuid = env->NewStringUTF(str); ALOGV("listModules module %d id %d description %s maxSoundModels %d", i, nModules[i].handle, nModules[i].properties.description, nModules[i].properties.max_sound_models); jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor, nModules[i].handle, implementor, description, uuid, nModules[i].properties.version, nModules[i].properties.max_sound_models, nModules[i].properties.max_key_phrases, nModules[i].properties.max_users, nModules[i].properties.recognition_modes, nModules[i].properties.capture_transition, nModules[i].properties.max_buffer_ms, nModules[i].properties.concurrent_capture, nModules[i].properties.power_consumption_mw); env->DeleteLocalRef(implementor); env->DeleteLocalRef(description); env->DeleteLocalRef(uuid); if (newModuleDesc == NULL) { status = SOUNDTRIGGER_STATUS_ERROR; goto exit; } env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc); } exit: free(nModules); return (jint) status; } static void android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz, jobject weak_this) { ALOGV("setup"); sp callback = new JNISoundTriggerCallback(env, thiz, weak_this); sound_trigger_module_handle_t handle = (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId); sp module = SoundTrigger::attach(handle, callback); if (module == 0) { return; } setSoundTrigger(env, thiz, module); } static void android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz) { ALOGV("detach"); sp module = setSoundTrigger(env, thiz, 0); ALOGV("detach module %p", module.get()); if (module != 0) { ALOGV("detach module->detach()"); module->detach(); } } static void android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz) { ALOGV("finalize"); sp module = getSoundTrigger(env, thiz); if (module != 0) { ALOGW("SoundTrigger finalized without being detached"); } android_hardware_SoundTrigger_detach(env, thiz); } static jint android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz, jobject jSoundModel, jintArray jHandle) { jint status = SOUNDTRIGGER_STATUS_OK; char *nData = NULL; struct sound_trigger_sound_model *nSoundModel; jbyteArray jData; sp memoryDealer; sp memory; size_t size; sound_model_handle_t handle; ALOGV("loadSoundModel"); sp module = getSoundTrigger(env, thiz); if (module == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } if (jHandle == NULL) { return SOUNDTRIGGER_STATUS_BAD_VALUE; } jsize jHandleLen = env->GetArrayLength(jHandle); if (jHandleLen == 0) { return SOUNDTRIGGER_STATUS_BAD_VALUE; } jint *nHandle = env->GetIntArrayElements(jHandle, NULL); if (nHandle == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) { status = SOUNDTRIGGER_STATUS_BAD_VALUE; goto exit; } size_t offset; sound_trigger_sound_model_type_t type; if (env->IsInstanceOf(jSoundModel, gKeyPhraseSoundModelClass)) { offset = sizeof(struct sound_trigger_phrase_sound_model); type = SOUND_MODEL_TYPE_KEYPHRASE; } else { offset = sizeof(struct sound_trigger_sound_model); type = SOUND_MODEL_TYPE_UNKNOWN; } jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data); if (jData == NULL) { status = SOUNDTRIGGER_STATUS_BAD_VALUE; goto exit; } size = env->GetArrayLength(jData); nData = (char *)env->GetByteArrayElements(jData, NULL); if (jData == NULL) { status = SOUNDTRIGGER_STATUS_ERROR; goto exit; } memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel"); if (memoryDealer == 0) { status = SOUNDTRIGGER_STATUS_ERROR; goto exit; } memory = memoryDealer->allocate(offset + size); if (memory == 0 || memory->pointer() == NULL) { status = SOUNDTRIGGER_STATUS_ERROR; goto exit; } nSoundModel = (struct sound_trigger_sound_model *)memory->pointer(); nSoundModel->type = type; nSoundModel->data_size = size; nSoundModel->data_offset = offset; memcpy((char *)nSoundModel + offset, nData, size); if (type == SOUND_MODEL_TYPE_KEYPHRASE) { struct sound_trigger_phrase_sound_model *phraseModel = (struct sound_trigger_phrase_sound_model *)nSoundModel; jobjectArray jPhrases = (jobjectArray)env->GetObjectField(jSoundModel, gKeyPhraseSoundModelFields.keyPhrases); if (jPhrases == NULL) { status = SOUNDTRIGGER_STATUS_BAD_VALUE; goto exit; } size_t numPhrases = env->GetArrayLength(jPhrases); phraseModel->num_phrases = numPhrases; ALOGV("loadSoundModel numPhrases %d", numPhrases); for (size_t i = 0; i < numPhrases; i++) { jobject jPhrase = env->GetObjectArrayElement(jPhrases, i); phraseModel->phrases[i].recognition_mode = env->GetIntField(jPhrase,gKeyPhraseFields.recognitionModes); phraseModel->phrases[i].num_users = env->GetIntField(jPhrase, gKeyPhraseFields.numUsers); jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyPhraseFields.locale); const char *nLocale = env->GetStringUTFChars(jLocale, NULL); strncpy(phraseModel->phrases[i].locale, nLocale, SOUND_TRIGGER_MAX_LOCALE_LEN); jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyPhraseFields.text); const char *nText = env->GetStringUTFChars(jText, NULL); strncpy(phraseModel->phrases[i].text, nText, SOUND_TRIGGER_MAX_STRING_LEN); env->ReleaseStringUTFChars(jLocale, nLocale); env->DeleteLocalRef(jLocale); env->ReleaseStringUTFChars(jText, nText); env->DeleteLocalRef(jText); ALOGV("loadSoundModel phrases %d text %s locale %s", i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale); } env->DeleteLocalRef(jPhrases); } status = module->loadSoundModel(memory, &handle); ALOGV("loadSoundModel status %d handle %d", status, handle); exit: if (nHandle != NULL) { nHandle[0] = (jint)handle; env->ReleaseIntArrayElements(jHandle, nHandle, NULL); } if (nData != NULL) { env->ReleaseByteArrayElements(jData, (jbyte *)nData, NULL); } return status; } static jint android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz, jint jHandle) { jint status = SOUNDTRIGGER_STATUS_OK; ALOGV("unloadSoundModel"); sp module = getSoundTrigger(env, thiz); if (module == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } status = module->unloadSoundModel((sound_model_handle_t)jHandle); return status; } static jint android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz, jint jHandle, jbyteArray jData) { jint status = SOUNDTRIGGER_STATUS_OK; ALOGV("startRecognition"); sp module = getSoundTrigger(env, thiz); if (module == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } jsize dataSize = 0; char *nData = NULL; sp memory; if (jData != NULL) { dataSize = env->GetArrayLength(jData); if (dataSize == 0) { return SOUNDTRIGGER_STATUS_BAD_VALUE; } nData = (char *)env->GetByteArrayElements(jData, NULL); if (nData == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } sp memoryDealer = new MemoryDealer(dataSize, "SoundTrigge-JNI::StartRecognition"); if (memoryDealer == 0) { return SOUNDTRIGGER_STATUS_ERROR; } memory = memoryDealer->allocate(dataSize); if (memory == 0 || memory->pointer() == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } memcpy(memory->pointer(), nData, dataSize); } status = module->startRecognition(jHandle, memory); return status; } static jint android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz, jint jHandle) { jint status = SOUNDTRIGGER_STATUS_OK; ALOGV("stopRecognition"); sp module = getSoundTrigger(env, thiz); if (module == NULL) { return SOUNDTRIGGER_STATUS_ERROR; } status = module->stopRecognition(jHandle); return status; } static JNINativeMethod gMethods[] = { {"listModules", "(Ljava/util/ArrayList;)I", (void *)android_hardware_SoundTrigger_listModules}, }; static JNINativeMethod gModuleMethods[] = { {"native_setup", "(Ljava/lang/Object;)V", (void *)android_hardware_SoundTrigger_setup}, {"native_finalize", "()V", (void *)android_hardware_SoundTrigger_finalize}, {"detach", "()V", (void *)android_hardware_SoundTrigger_detach}, {"loadSoundModel", "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I", (void *)android_hardware_SoundTrigger_loadSoundModel}, {"unloadSoundModel", "(I)I", (void *)android_hardware_SoundTrigger_unloadSoundModel}, {"startRecognition", "(I[B)I", (void *)android_hardware_SoundTrigger_startRecognition}, {"stopRecognition", "(I)I", (void *)android_hardware_SoundTrigger_stopRecognition}, }; int register_android_hardware_SoundTrigger(JNIEnv *env) { jclass arrayListClass = env->FindClass("java/util/ArrayList"); gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass); gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); jclass lClass = env->FindClass(kSoundTriggerClassPathName); gSoundTriggerClass = (jclass) env->NewGlobalRef(lClass); jclass moduleClass = env->FindClass(kModuleClassPathName); gModuleClass = (jclass) env->NewGlobalRef(moduleClass); gPostEventFromNative = env->GetStaticMethodID(moduleClass, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); gModuleFields.mNativeContext = env->GetFieldID(moduleClass, "mNativeContext", "J"); gModuleFields.mId = env->GetFieldID(moduleClass, "mId", "I"); jclass modulePropertiesClass = env->FindClass(kModulePropertiesClassPathName); gModulePropertiesClass = (jclass) env->NewGlobalRef(modulePropertiesClass); gModulePropertiesCstor = env->GetMethodID(modulePropertiesClass, "", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZI)V"); jclass soundModelClass = env->FindClass(kSoundModelClassPathName); gSoundModelClass = (jclass) env->NewGlobalRef(soundModelClass); gSoundModelFields.data = env->GetFieldID(soundModelClass, "data", "[B"); jclass keyPhraseClass = env->FindClass(kKeyPhraseClassPathName); gKeyPhraseClass = (jclass) env->NewGlobalRef(keyPhraseClass); gKeyPhraseFields.recognitionModes = env->GetFieldID(keyPhraseClass, "recognitionModes", "I"); gKeyPhraseFields.locale = env->GetFieldID(keyPhraseClass, "locale", "Ljava/lang/String;"); gKeyPhraseFields.text = env->GetFieldID(keyPhraseClass, "text", "Ljava/lang/String;"); gKeyPhraseFields.numUsers = env->GetFieldID(keyPhraseClass, "numUsers", "I"); jclass keyPhraseSoundModelClass = env->FindClass(kKeyPhraseSoundModelClassPathName); gKeyPhraseSoundModelClass = (jclass) env->NewGlobalRef(keyPhraseSoundModelClass); gKeyPhraseSoundModelFields.keyPhrases = env->GetFieldID(keyPhraseSoundModelClass, "keyPhrases", "[Landroid/hardware/soundtrigger/SoundTrigger$KeyPhrase;"); jclass recognitionEventClass = env->FindClass(kRecognitionEventClassPathName); gRecognitionEventClass = (jclass) env->NewGlobalRef(recognitionEventClass); gRecognitionEventCstor = env->GetMethodID(recognitionEventClass, "", "(IIZII[B)V"); jclass keyPhraseRecognitionEventClass = env->FindClass(kKeyPhraseRecognitionEventClassPathName); gKeyPhraseRecognitionEventClass = (jclass) env->NewGlobalRef(keyPhraseRecognitionEventClass); gKeyPhraseRecognitionEventCstor = env->GetMethodID(keyPhraseRecognitionEventClass, "", "(IIZII[BZ[Landroid/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionExtra;)V"); jclass keyPhraseRecognitionExtraClass = env->FindClass(kKeyPhraseRecognitionExtraClassPathName); gKeyPhraseRecognitionExtraClass = (jclass) env->NewGlobalRef(keyPhraseRecognitionExtraClass); gKeyPhraseRecognitionExtraCstor = env->GetMethodID(keyPhraseRecognitionExtraClass, "", "([II)V"); int status = AndroidRuntime::registerNativeMethods(env, kSoundTriggerClassPathName, gMethods, NELEM(gMethods)); if (status == 0) { status = AndroidRuntime::registerNativeMethods(env, kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods)); } return status; }