Невозможно получить значение JNIEnv * в произвольном контексте - PullRequest
5 голосов
/ 13 мая 2011

У меня проблема с NDK.

В моем методе JNI_OnLoad я кеширую указатель JavaVm, класс, который вызвал метод, и идентификатор метода, который я использую позже:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved){
    JNIEnv *env;
    cachedJVM = jvm;
    if((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6)){
        LOG_ERROR("Could not get JNIEnv*");
        return JNI_ERR;
    }
    javaClass = (*env)->FindClass(env, "org/test/opensl/AudioProcessor");
    if(javaClass == NULL){
        LOG_ERROR("Could not get java class");
        return JNI_ERR;
    }
    javaCallbackMID = (*env)->GetMethodID(env, javaClass, "enqueueAudio", "([B)V");
    if(javaCallbackMID == NULL){
        LOG_ERROR("Could not get method identifier");
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

У меня есть небольшой служебный метод, определенный следующим образом, который должен получить указатель на JNIEnv:

JNIEnv* JNU_GetEnv(){
    JNIEnv* env;
    (*cachedJVM)->GetEnv(cachedJVM, (void**)&env, JNI_VERSION_1_6);
    return env;
}

И, наконец, у меня есть обратный вызов от OpenSL ES SLAndroidSimpleBufferQueueItf, который я хочу обработать записанный звук с SLRecordItf:

void recorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
    SLresult result;
    JNIEnv* env;
    recorderContext* thisContext = (recorderContext*)context;
    env = JNU_GetEnv();
    if(env == NULL){
        LOG_ERROR("Could not get JNIEnv*");
        return;
    }
    jbyteArray data = (*env)->NewByteArray(env, MAX_PACKET_SIZE);
    if(data == NULL){
        LOG_ERROR("No memory could be allocated for buffer");
        return;
    }
    (*env)->SetByteArrayRegion(env, data, 0, MAX_PACKET_SIZE, recorderBuffer);
    (*env)->CallByteMethodA(env, thisContext->caller, javaCallbackMID, data);
    (*env)->DeleteLocalRef(env, data);
    result = (*bq)->Enqueue(bq, recorderBuffer,
                            RECORDER_FRAMES * sizeof(jbyte));
    checkError(result, "Unable to enqueue new buffer");
}

Где параметр контекста для метода обратного вызова содержит только ссылку на объект, который вызвал собственный метод. Это самостоятельная структура, подобная этой:

typedef struct recorderContext{
    jobject caller;
} recorderContext;

Однако, каждый раз, когда я пытаюсь выполнить это, я получаю сообщение об ошибке "Could not get JNIEnv*" из метода обратного вызова.

Мой вопрос в основном сводится к следующему: почему я могу получить указатель на JNIEnv в методе JNI_OnLoad, но не в RecorderCallback, так как оба используют один и тот же указатель Java VM для получения JNIEnv?

Мне нужен этот обратный вызов для передачи записанного аудио обратно на уровень Java для дальнейшей обработки ...

1 Ответ

10 голосов
/ 13 мая 2011

Почему я могу получить указатель на JNIEnv в методе JNI_OnLoad, но не в RecorderCallback, так как оба используют тот же указатель Java VM, чтобы получить JNIEnv

Поскольку обратный вызов происходит в каком-то собственном потоке, отличном от потока ВМ, который загружает библиотеку. Реализация JNI поддерживает JNIEnv на поток и помещает указатель в локальное хранилище потока. Он инициализируется только для собственных потоков, которые подключены к виртуальной машине. Вам нужно вызвать AttachCurrentThread() (или, скорее всего, AttachCurrentThreadAsDaemon()) внутри обратного вызова, чтобы получить действительный указатель JNIEnv для этого потока. Это присоединяет поток к ВМ при первом вызове и после этого не выполняется.

См. http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html

Предостережение: Этот ответ предполагает правильную Java. Проблемы, которые вы видите, позволяют предположить, что Dalvik ведет себя так же, как JVM.

...