【问题标题】:How To Call JAVA Methods from inside of a Thread in JNI如何从 JNI 中的线程内部调用 JAVA 方法
【发布时间】:2020-03-11 12:48:06
【问题描述】:

TL;DR;我在通过线程将 FFMPEG 原始数据从 C++ 代码传递到 JAVA 代码以进行显示时遇到问题。

有一个服务器设置向其客户端发送编码帧。这些编码帧是用一些 FFMPEG 魔法编码的。在客户端收到时,上述帧将被解码为原始 RGB 数据(作为无符号字符 *)。现在的问题是帧是在某种“侦听器”中接收的。只是一个在后台运行的线程轮询服务器并在新帧可用时运行特定的onFrame 函数。

当前以视频格式显示帧的解决方案是将每个帧保存到 C++ 的内部存储中,然后在 java 端有一个FileObserver,一旦图像写入内存就会显示图像。遗憾的是,这种方法在手机上生成了 6 FPS 的视频,而在服务器上生成了 10 FPS 的视频。

我需要一种将 unsigned char * (jbytearray) 传递给我的 JAVA 代码的方法,以便我可以对其进行解码并从 RAM 而不是磁盘显示它。

值得一提的是,onFrame 函数的参数列表中不能有 JNIEnv* && jobject(库要求)。

到目前为止,我尝试的是在我的 MainActivity 中创建一个本机方法,通过它我传递 JNIEnvjobject 并将它们分配给全局变量

JNIEnv* m_globalEnv = env;
jobject m_globalObject = thiz;
JavaVM m_jvm = 0;
jclass mainActivity = m_globalEnv->GetObjectClass(m_globalObject);
jmethodID testMethod = m_globalEnv->GetMethodID(mainClass, "testMethod", "(I)V");

m_globalEnv->GetJavaVM(&m_jvm);

之后,我在我的onFrame 中调用
jvm->AttachCurrentThread(&m_globalEnv, NULL);
然后我尝试从代码内部的某个地方调用一个 JAVA 方法(这与我在 onFrame 中调用它的位置/时间无关):

m_globalEnv->CallVoidMethod(m_globalObject, "testMethod", 5);

然后所有崩溃:

1- JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0xffe8ea7c
2- JNI DETECTED ERROR IN APPLICATION: Thread is making JNI calls without being attached
.
.
.

编辑 1

在尝试了 Michael 解决方案中的代码后,我收到了
java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0xc94f7f8c 错误。 在调试模式下运行应用程序以捕获错误后,我到达了jni.h;触发错误的代码行是: m_env->CallVoidMethod(m_globalObject, testMethod, 5); (5 是我试图通过用于测试目的的数字)。 jni.h 中被调试器突出显示的代码行位于
void CallVoidMethod(jobject obj, jmethodID methodID, ...)
它是 functions->CallVoidMethodV(this, obj, methodID, args);
在第 228 行定义: void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);

【问题讨论】:

  • m_globalEnv 的意义何在?您不能跨线程共享JNIEnv*,那么为什么它需要是全局的?
  • 不管怎样,你说你正在调用AttachCurrentThread,但是日志说线程没有附加。因此,您可能在代码的某些部分中存在未向我们展示的错误。
  • @Michael 由于我无法在onFrame 中传递JNIEnv*。所以我做了一个全局参考,以后每次收到时都会调用它。我还尝试通过获取对全局的引用,为每一帧在线程中创建一个新的 Env。我可能在这里采取了错误的方法,而不是否认这一点。
  • 也许stackoverflow.com/questions/30026030/… 可以提供帮助。
  • thiz 可能是一个陈旧的本地引用。您应该在分配 m_globalObject 时使用 NewGlobalRef 将其设为全局引用,然后在本机代码中不再需要该对象时使用 DeleteGlobalRef 释放它。

标签: java android c++ ffmpeg java-native-interface


【解决方案1】:

我发现代码有两个潜在问题:

1.跨线程共享JNIEnv*
每个本机线程都应该通过将自己附加到 JVM 来获得自己的JNIEnv*,然后在某个时候将自己分离。有关更多详细信息和可能的解决方案,请参阅this answer

2。缓存本地引用
作为本机函数的第二个参数收到的 thiz 引用是本地引用,调用 JNI 函数返回的大多数 jobjects 也是本地引用。
本地引用仅在“从它最初交给的线程中可用,并且在显式调用 DeleteLocalRef() 或更常见的情况下,直到你从本机方法返回”之前一直有效。

如果你想从另一个线程使用那个对象,你需要从本地引用创建一个全局引用:

m_globalObject = NewGlobalRef(thiz);

当您不再需要在本机代码中的任何位置使用该对象时,请记住删除全局引用 (DeleteGlobalRef(m_globalObject))。否则可能会导致内存泄漏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多