Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Multithreaded Rendering JNI C++ to call Java function not work

Discussion in 'Android' started by douysu, May 17, 2022.

  1. douysu

    douysu

    Joined:
    May 4, 2022
    Posts:
    5
    I want to use Unity to render the resources in my Android and control the logic on the Android device. I have adopted the following structure. It normal work at the single thread but not work at Multithreaded rendering. I want the architecture normal run at Multithreaded rendering, so i want to use GL.IssuePluginEvent to run at rendering thread.

    My C# code is
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using System;
    6. using AOT;
    7. using System.Runtime.InteropServices;
    8.  
    9. public class Main : MonoBehaviour
    10. {
    11.  
    12.     private AndroidJavaObject nativeObject;
    13.     private static int width, height;
    14.     public MeshRenderer meshRenderer;
    15.     private Texture2D texture;
    16.     private delegate void RenderEventDelegate(int eventID);
    17.     private RenderEventDelegate RenderThreadHandle;
    18.     private IntPtr RenderThreadHandlePtr;
    19.     private static int texturePtr;
    20.  
    21.     void Awake()
    22.     {
    23.         RenderThreadHandle = new RenderEventDelegate(RunOnRenderThread);
    24.         RenderThreadHandlePtr = Marshal.GetFunctionPointerForDelegate(RenderThreadHandle);
    25.     }
    26.  
    27.     // Use this for initialization
    28.     void Start()
    29.     {
    30.         Debug.Log("OnStart");
    31.         AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    32.         AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
    33.         nativeObject = new AndroidJavaObject("com.pvr.videoplugin.VideoPlugin", jo);
    34.         width = 1600;
    35.         height = 900;
    36.         Debug.Log("VideoPlugin:" + width + ", " + height);
    37.  
    38.         nativeObject.CallStatic("loadLib");
    39.  
    40.         nativeObject.Call("initObject");
    41.  
    42.         if (SystemInfo.graphicsMultiThreaded)
    43.         {
    44.             Debug.Log("VideoPlugin:Start");
    45.             texture = new Texture2D(width, height, TextureFormat.RGB24, false, false);
    46.             texturePtr = (int)texture.GetNativeTexturePtr();
    47.             GL.IssuePluginEvent(RenderThreadHandlePtr, GL_INIT_EVENT);
    48.             meshRenderer.material.mainTexture = texture;
    49.         }
    50.         else
    51.         {
    52.             Debug.Log("VideoPlugin:Start");
    53.             texture = new Texture2D(width, height, TextureFormat.RGB24, false, false);
    54.             texturePtr = (int)texture.GetNativeTexturePtr();
    55.             RunOnRenderThread(GL_INIT_EVENT);
    56.             meshRenderer.material.mainTexture = texture;
    57.         }
    58.     }
    59.  
    60.     private void OnDisable()
    61.     {
    62.         Debug.Log("OnDisable");
    63.         if (SystemInfo.graphicsMultiThreaded)
    64.         {
    65.             texture = null;
    66.             meshRenderer.material.mainTexture = null;
    67.             GL.IssuePluginEvent(RenderThreadHandlePtr, GL_DESTROY_EVENT);
    68.         }
    69.         else
    70.         {
    71.             texture = null;
    72.             meshRenderer.material.mainTexture = null;
    73.             RunOnRenderThread(GL_DESTROY_EVENT);
    74.         }
    75.     }
    76.    
    77.     void Update()
    78.     {
    79.         if (SystemInfo.graphicsMultiThreaded)
    80.         {
    81.             GL.IssuePluginEvent(RenderThreadHandlePtr, GL_UPDATE_EVENT);
    82.             GL.InvalidateState();
    83.         }
    84.         else
    85.         {
    86.             RunOnRenderThread(GL_UPDATE_EVENT);
    87.             GL.InvalidateState();
    88.         }
    89.     }
    90.    
    91.     private void updateTexutreCsharp()
    92.     {
    93.         if (texture != null && nativeObject.Call<bool>("isUpdateFrame"))
    94.         {
    95.             Debug.Log("VideoPlugin:Update");
    96.             updateTexture();
    97.             //nativeObject.Call("updateTexture");
    98.             GL.InvalidateState();
    99.         }
    100.     }
    101.    
    102.     private void createTexture()
    103.     {
    104.         Debug.Log("VideoPlugin:Start");
    105.         texture = new Texture2D(width, height, TextureFormat.RGB24, false, false);
    106.         start((int)texture.GetNativeTexturePtr(), width, height);
    107.         // nativeObject.Call("start", (int)texture.GetNativeTexturePtr(), width, height);
    108.         meshRenderer.material.mainTexture = texture;
    109.     }
    110.    
    111.     private void destroyTexture()
    112.     {
    113.         Debug.Log("VideoPlugin:Release");
    114.         texture = null;
    115.         meshRenderer.material.mainTexture = null;
    116.         release();
    117.         //nativeObject.Call("release");
    118.     }
    119.    
    120.     private const int GL_INIT_EVENT = 0x0001;
    121.     private const int GL_UPDATE_EVENT = 0x0002;
    122.     private const int GL_DESTROY_EVENT = 0x0003;
    123.     [MonoPInvokeCallback(typeof(RenderEventDelegate))]
    124.     private static void RunOnRenderThread(int eventID)
    125.     {
    126.         switch (eventID)
    127.         {
    128.             case GL_INIT_EVENT:
    129.                 start(texturePtr, width, height);
    130.                 break;
    131.             case GL_UPDATE_EVENT:
    132.                 updateTexture();
    133.                 break;
    134.             case GL_DESTROY_EVENT:
    135.                 release();
    136.                 break;
    137.         }
    138.     }
    139.  
    140.     [DllImport("application")]
    141.     private static extern void start(int unityTextureId, int width, int height);
    142.  
    143.     [DllImport("application")]
    144.     private static extern void release();
    145.  
    146.     [DllImport("application")]
    147.     private static extern void updateTexture();
    148. }
    149.  
    C++ code is
    Code (CSharp):
    1. #include "jni.h"
    2. #include <android/log.h>
    3. #include <cstdio>
    4. #include <pthread.h>
    5. #include <unistd.h>
    6.  
    7. #define ehome_printf(format, ...)  \
    8.     __android_log_print(ANDROID_LOG_DEBUG,  "jni_debug", format, ##__VA_ARGS__)
    9.  
    10. static JavaVM *ms2_vm = NULL;
    11. static jobject g_obj;
    12.  
    13. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
    14. {
    15.     ms2_vm = vm;
    16.     ehome_printf("[%s]getpid=%d, gettid=%d\n", __FUNCTION__, getpid(),gettid());
    17.     return JNI_VERSION_1_4;
    18. }
    19.  
    20. extern "C"
    21. {
    22.     bool get_env(JNIEnv ** env) {
    23.         int status = ms2_vm->GetEnv((void**) env, JNI_VERSION_1_4);
    24.         if (status != JNI_OK) {
    25.             ehome_printf("[%s]getpid=%d, gettid=%d\n", __FUNCTION__, getpid(),gettid());
    26.             status = ms2_vm->AttachCurrentThread(env, NULL);
    27.             if(status != JNI_OK){
    28.                 ehome_printf("[%s]FAILED\n", __FUNCTION__);
    29.                 return false;
    30.             }
    31.             ehome_printf("[%s]SUCCESS\n", __FUNCTION__);
    32.         }else{
    33.             ehome_printf("[%s]Attach aready\n", __FUNCTION__);
    34.         }
    35.         return true;
    36.     }
    37.  
    38.     void release_env(void) {
    39.         JNIEnv *env ;
    40.         int status = ms2_vm->GetEnv((void**)&env, JNI_VERSION_1_4);
    41.         if (status == JNI_EDETACHED) {
    42.             ehome_printf("[%s]getpid=%d, gettid=%d\n", __FUNCTION__, getpid(),gettid());
    43.             ms2_vm->DetachCurrentThread();
    44.             ehome_printf("Release success");
    45.         }else{
    46.             ehome_printf("[%s]NEED NOT DETACH\n", __FUNCTION__);
    47.         }
    48.     }
    49.  
    50.     void start(int unityTextureId, int width, int height)
    51.     {
    52.         ehome_printf("start function is start");
    53.         JNIEnv *env;
    54.         ehome_printf("get env start");
    55.         if (!get_env(&env)) {
    56.             ehome_printf("[%s]get_env error!\n", __FUNCTION__);
    57.             return;
    58.         }
    59.         ehome_printf("[%s]GetVersion=%d\n", __FUNCTION__, env->GetVersion());
    60.  
    61.         jclass clazz = env->FindClass("com/pvr/videoplugin/VideoPlugin");
    62.         ehome_printf("FindClass end");
    63.         jmethodID mt = env->GetMethodID(clazz, "start", "(III)V");
    64.         ehome_printf("GetMethodID end");
    65.         env->CallVoidMethod(g_obj, mt, unityTextureId, width, height);
    66.         ehome_printf("CallVoidMethod end");
    67.  
    68.         release_env();
    69.         ehome_printf("start function is end");
    70.     }
    71.  
    72.     void release()
    73.     {
    74.         ehome_printf("release function is start");
    75.         JNIEnv *env;
    76.         if (!get_env(&env)) {
    77.             ehome_printf("[%s]get_env error!\n", __FUNCTION__);
    78.             return;
    79.         }
    80.         ehome_printf("[%s]GetVersion=%d\n", __FUNCTION__, env->GetVersion());
    81.  
    82.         jclass clazz = env->FindClass("com/pvr/videoplugin/VideoPlugin");
    83.         jmethodID mt = env->GetMethodID(clazz, "release", "()V");
    84.         env->CallVoidMethod(g_obj, mt);
    85.  
    86.         // release_env();
    87.         ehome_printf("release function is end");
    88.     }
    89.  
    90.     void updateTexture()
    91.     {
    92.         ehome_printf("updateTexture function is start");
    93.         JNIEnv *env;
    94.         if (!get_env(&env)) {
    95.             ehome_printf("[%s]get_env error!\n", __FUNCTION__);
    96.             return;
    97.         }
    98.         ehome_printf("[%s]GetVersion=%d\n", __FUNCTION__, env->GetVersion());
    99.  
    100.         jclass clazz = env->FindClass("com/pvr/videoplugin/VideoPlugin");
    101.         jmethodID mt = env->GetMethodID(clazz, "updateTexture", "()V");
    102.         env->CallVoidMethod(g_obj, mt);
    103.  
    104.         // release_env();
    105.         ehome_printf("updateTexture function is end");
    106.     }
    107. }
    108.  
    109. extern "C" JNIEXPORT void JNICALL
    110. Java_com_pvr_videoplugin_VideoPlugin_initObject(
    111.         JNIEnv* env,
    112. jobject obj) {
    113.     ehome_printf("InitObject Function is run");
    114.     g_obj = env->NewGlobalRef(obj);
    115. }
    My java code is
    Code (CSharp):
    1. package com.pvr.videoplugin;
    2.  
    3. import android.app.Activity;
    4. import android.content.res.AssetFileDescriptor;
    5. import android.graphics.SurfaceTexture;
    6. import android.graphics.SurfaceTexture.OnFrameAvailableListener;
    7. import android.media.AudioManager;
    8. import android.media.MediaPlayer;
    9. import android.net.Uri;
    10. import android.view.Surface;
    11.  
    12. import java.io.File;
    13. import java.io.IOException;
    14.  
    15. public class VideoPlugin implements OnFrameAvailableListener {
    16.  
    17.     private final Activity mActivity;
    18.     private SurfaceTexture mSurfaceTexture;
    19.     private Surface mSurface;
    20.     private FilterFBOTexture mFilterFBOTexture;
    21.     private MediaPlayer mMediaPlayer;
    22.     private boolean mIsUpdateFrame;
    23.  
    24.     public VideoPlugin(Activity activity) {
    25.         FBOUtils.log("VideoPlugin is start");
    26.         mActivity = activity;
    27.         FBOUtils.log("VideoPlugin is end");
    28.     }
    29.  
    30.     public void start(int unityTextureId, int width, int height) {
    31.         FBOUtils.log("start, unityTextureId=" + unityTextureId);
    32.  
    33.         int videoTextureId = FBOUtils.createOESTextureID();
    34.  
    35.         mSurfaceTexture = new SurfaceTexture(videoTextureId);
    36.         mSurfaceTexture.setDefaultBufferSize(width, height);
    37.         mSurfaceTexture.setOnFrameAvailableListener(this);
    38.  
    39.         mSurface = new Surface(mSurfaceTexture);
    40.  
    41.         mFilterFBOTexture = new FilterFBOTexture(width, height, unityTextureId, videoTextureId);
    42.  
    43.         initMediaPlayer();
    44.     }
    45.  
    46.     public void release() {
    47.         FBOUtils.log("release");
    48.         if (mMediaPlayer != null) {
    49.             mMediaPlayer.stop();
    50.             mMediaPlayer.reset();
    51.             mMediaPlayer.release();
    52.             mMediaPlayer = null;
    53.         }
    54.         if (mSurface != null) {
    55.             mSurface.release();
    56.             mSurface = null;
    57.         }
    58.         if (mSurfaceTexture != null) {
    59.             mSurfaceTexture.release();
    60.             mSurfaceTexture = null;
    61.         }
    62.         if (mFilterFBOTexture != null) {
    63.             mFilterFBOTexture.release();
    64.             mFilterFBOTexture = null;
    65.         }
    66.         mIsUpdateFrame = false;
    67.     }
    68.  
    69.     private void initMediaPlayer() {
    70.         mMediaPlayer = new MediaPlayer();
    71.         mMediaPlayer.setSurface(mSurface);
    72.         try {
    73.             AssetFileDescriptor fd = mActivity.getAssets().openFd("test.mp4");
    74. //            final File file = new File("/sdcard/test.mp4");
    75.             mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    76.             mMediaPlayer.setLooping(true);
    77. //            mMediaPlayer.setDataSource(Uri.fromFile(file).toString());
    78.             mMediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
    79.             mMediaPlayer.prepareAsync();
    80.         } catch (IOException e) {
    81.             e.printStackTrace();
    82.         }
    83.         mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
    84.             @Override
    85.             public void onPrepared(MediaPlayer mp) {
    86.                 FBOUtils.log("MediaPlayer onPrepared");
    87.                 mMediaPlayer.start();
    88.             }
    89.         });
    90.     }
    91.  
    92.     @Override
    93.     public void onFrameAvailable(SurfaceTexture surfaceTexture) {
    94.         FBOUtils.log("onFrameAvailable");
    95.         mIsUpdateFrame = true;
    96.     }
    97.  
    98.     public void updateTexture() {
    99.         FBOUtils.log("updateTexture");
    100.         mIsUpdateFrame = false;
    101.         mSurfaceTexture.updateTexImage();
    102.         mFilterFBOTexture.draw();
    103.     }
    104.  
    105.     public boolean isUpdateFrame() {
    106.         return mIsUpdateFrame;
    107.     }
    108.  
    109.     // native function
    110.     public native void initObject();
    111.  
    112. //    static {
    113. //        System.loadLibrary("application");
    114. //    }
    115.  
    116.     public static void loadLib() {
    117.         System.loadLibrary("application");
    118.         FBOUtils.log("Lod .so end");
    119.     }
    120. }
    121.  
    my Unity setting is
    upload_2022-5-17_17-15-29.png
    upload_2022-5-17_17-15-46.png
     

    Attached Files:

  2. douysu

    douysu

    Joined:
    May 4, 2022
    Posts:
    5
    the logcat is this
    upload_2022-5-17_17-23-43.png

    Failed to load native plugin: Unable to load library '/data/app/com.pvr.easy-wpe-VykhgfdNXpjQCBa40Q==/lib/arm/libapplication.so', error 'java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.ClassLoader java.lang.Class.getClassLoader()' on a null object reference'
     
  3. douysu

    douysu

    Joined:
    May 4, 2022
    Posts:
    5
    How can I deal with this problem.thanks
     
  4. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,653
    I think your problem comes from bootstrap classloader, which is what is used when you AttachCurrentThread to JNI.
    In Unity code when such failure occurs we fallback to using class loader from activity class.
    We have this C# API which should find a class: https://docs.unity3d.com/ScriptReference/AndroidJNI.FindClass.html
    It returns the jclass.
     
  5. douysu

    douysu

    Joined:
    May 4, 2022
    Posts:
    5
    Yes, the failure occurs when i use unity render thread AttachCurrentThread and env->FindClass env->GetMethodID. The error not occur at unity Main Thread.

    I tried to use AndroidJNI.FindClass but seem not work, I don't know how to use it in detail in my project

    How can i use AndroidJNI.FindClass, should i find the class at unity and send it to JNI?
     
  6. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,653
    Try using reverse P/Invoke (MonoPInvokeCallback().
    Or you can call it in C#, then do a NewGlobalRef on it and pass to you code. You need a global ref, since local refs can't used by a different thread.
     
  7. douysu

    douysu

    Joined:
    May 4, 2022
    Posts:
    5
    thanks, this bug has been solved. I use a global ref.
     
  8. cbmjm

    cbmjm

    Joined:
    Jun 2, 2022
    Posts:
    1
    Hello, I have a question, now I call AndroidJNI.FindClass and AndroidJNI.NewGlobalRef in the c# layer, should this new variable be set as global? And I'm a novice, would like to ask how c# should pass this variable to the c++ layer, thank you very much, if you can guide me, I will be very grateful