6

TL;DR; I'm having a problem with passing my FFMPEG raw data from C++ code to JAVA code, for displaying, through a thread.

There is a server set up that sends out encoded frames to its clients. Those encoded frames are encoded with some FFMPEG magic. When received on client side, the fore-mentioned frames are getting decoded into raw RGB data (as a unsigned char *). The problem now is that frames are being received in a "listener" of sorts. Just a thread running in the background polling the server and running specific onFrame function once a new frame is available.

The current solution for displaying the frames in a video-format is to save each frame to internal storage in C++, and then have a FileObserver on java side that displays an image as soon as it's written in the memory. Sadly, that approach yields a 6 FPS video on phone, for a 10 FPS video from Server.

I need a way of passing that unsigned char * (jbytearray) to my JAVA code so I can decode it and display it from RAM rather than Disk.

It's worth mentioning that onFrame function cannot have JNIEnv* && jobject inside it's arguments list (Library requirements).

What I have tried so far is making a native method in my MainActivity through which I pass JNIEnv and jobject and assign those to global variables

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);

After that, in my onFrame I call
jvm->AttachCurrentThread(&m_globalEnv, NULL);
And then I try to call a JAVA method from somewhere inside the code (It's irrelevant where/when in the onFrame I call it) by doing:

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

And then all crashes with either:

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
.
.
.

EDIT 1

After Trying out the code from Michael's solution, I got the
java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0xc94f7f8c error. After running the app in debug mode to catch the error, I got to the jni.h; Line of code that triggers the error is: m_env->CallVoidMethod(m_globalObject, testMethod, 5); (5 being the number I am trying to pass for testing purposes). The line of code inside jni.h that is being highlighted by the debugger is inside of
void CallVoidMethod(jobject obj, jmethodID methodID, ...)
and it's functions->CallVoidMethodV(this, obj, methodID, args);
which is defined on line 228: void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);

Community
  • 1
  • 1
Falcuun
  • 97
  • 8
  • 1
    What's the point of `m_globalEnv`? You cannot share a `JNIEnv*` across threads, so why would it need to be global? – Michael Nov 15 '19 at 11:12
  • Anyway, you say that you're calling `AttachCurrentThread`, but the logs say that the thread is not attached. So you probably have a bug in some part of the code that you haven't shown us. – Michael Nov 15 '19 at 11:16
  • @Michael Due to the fact that I cannot pass a ```JNIEnv*``` inside my ```onFrame```. So I make a global reference that I later call every time frame is received. I also tried creating a new Env in thread for every frame by grabbing a reference to global one. I might be having a wrong approach here, not denying that. – Falcuun Nov 15 '19 at 11:17
  • 1
    Maybe https://stackoverflow.com/questions/30026030/what-is-the-best-way-to-save-jnienv/30026231#30026231 can be of help. – Michael Nov 15 '19 at 11:23
  • @Michael After looking into that answer, and doing some modifying to fit my code, error I'm getting is: ```java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0xc94f7f8c``` – Falcuun Nov 15 '19 at 11:47
  • Impossible to say why without more info. Which line of code triggers the error? – Michael Nov 15 '19 at 11:50
  • @Michael I edited the Question a tad to provide a bit more detail of what information I could get a hold of. – Falcuun Nov 15 '19 at 12:12
  • 1
    `thiz` is probably a stale local reference. You should make `m_globalObject` a global reference using `NewGlobalRef` when you assign it, and then free it using `DeleteGlobalRef` when you no longer need that object anywhere in your native code. – Michael Nov 15 '19 at 12:46
  • @Michael Could you please post this as an answer so that I can accept it as an answer? You just solved something I've been stuck on for past 2 weeks and probably saved me from being fired! – Falcuun Nov 15 '19 at 12:49
  • Please read [Under what circumstances may I add “urgent” or other similar phrases to my question, in order to obtain faster answers?](//meta.stackoverflow.com/q/326569) - the summary is that this is not an ideal way to address volunteers, and is probably counterproductive to obtaining answers. Please refrain from adding this to your questions. – halfer Nov 16 '19 at 14:06

1 Answers1

4

I see two potential issues with the code:

1. Sharing a JNIEnv* across threads
Each native thread should obtain its own JNIEnv* by attaching itself to the JVM, and then detaching itself at some point. See this answer for more details and possible solutions.

2. Caching local references
The thiz reference you receive as the second argument to a native function is a local reference, as are most of the jobjects returned from calling JNI functions.
A local reference is usable only "from the thread it was originally handed to, and is valid until either an explicit call to DeleteLocalRef() or, more commonly, until you return from your native method".

If you want to use that object from another thread you need to create a global reference from the local reference:

m_globalObject = NewGlobalRef(thiz);

Remember to delete the global reference (DeleteGlobalRef(m_globalObject)) when you no longer need to use that object anywhere in your native code. Otherwise you may cause a memory leak.

Michael
  • 57,169
  • 9
  • 80
  • 125