0

My application needs to send samples recorded by audiorecord in java layer to JNI.

But to avoid memory operations(copy) multiple times, I created the buffer during initialization in the Native layer and passing the same to java layer as below.

Application.c

#include <jni.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>

#include <android/log.h>
#include "Application.h"

JNIEnv *Native_env;
jobject native_obj;

jmethodID WriteID, ReadID;
jclass JavaClass;
jshortArray ShortArray;

FILE *read_fptr;
//jsize start = 0;
//jsize leng = 0;
jshort NativeArray[1920];

void Update_NativeJNVVariables(JNIEnv *env, jobject obj)
{
    Native_env = (JNIEnv * )env;
    native_obj = obj;

    //ShortArray = (*Native_env)->NewShortArray(Native_env, 1920);

    //leng = (*Native_env)->GetArrayLength(Native_env,ShortArray);

    //__android_log_print(ANDROID_LOG_ERROR, "Update_NativeJNVVariables", "Length of array is %d", leng);

    memset(NativeArray, 0x00, sizeof(NativeArray));

    //(*Native_env)->SetShortArrayRegion(Native_env,ShortArray, start, leng, NativeArray);

    //jclass LocalClass;
    //LocalClass = (*Native_env)->GetObjectClass(Native_env,native_obj);
    //WriteClass = (*Native_env)->NewGlobalRef(Native_env,LocalClass);

    JavaClass = (*Native_env)->FindClass(Native_env, "com/consilient/tech/uday/javaapi/MainActivity");

    //LocalClass = (*Native_env)->GetObjectClass(Native_env,native_obj);
    //ReadClass = (*Native_env)->NewGlobalRef(Native_env,LocalClass);

   if(JavaClass != NULL)
   {
       WriteID = (*Native_env)->GetMethodID(Native_env,JavaClass,"WriteAndroidPCM","([B)V");
   }

   if(JavaClass != NULL)
   {
       //ReadID = (*Native_env)->GetMethodID(Native_env,JavaClass,"ReadAndroidPCM","([SI)V");
       ReadID = (*Native_env)->GetMethodID(Native_env,JavaClass,"ReadAndroidPCM","(I)V");
   }

    read_fptr = fopen("/sdcard/outputs/read_file.pcm","wb");
    if(read_fptr == NULL)
   {
        __android_log_print(ANDROID_LOG_ERROR, "UPdateJavaEnv", "Read FIle Cannot Be opened");
   }
}

void ReadPCMSamples(short *Buffer, int no_of_samples)
{
    //    jboolean isCopy = JNI_TRUE;
    //    jshort *ArrayPointer = (*Native_env)->GetShortArrayElements(Native_env,ShortArray, &isCopy);

    if(ReadID)
        (*Native_env)->CallVoidMethod(Native_env,JavaClass,ReadID,no_of_samples);

    //fwrite(ArrayPointer, sizeof(short), 160, read_fptr);

    //(*Native_env)->ReleaseShortArrayElements( Native_env, ShortArray, ArrayPointer, 0 );

    //(*Native_env)->GetDirectBufferAddress(Native_env,native_obj);

    //GetByteArray(Buffer, no_of_samples);
}

/*
 * Class:     com_consilient_tech_uday_javaapi_MainActivity
 *  Method:    StartNativeThread
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_consilient_tech_uday_javaapi_MainActivity_FillNativeBuffer
    (JNIEnv *env, jobject obj, jshortArray Buffer, int no_of_samples)
{
      jboolean isCopy = JNI_TRUE;
      jshort *ArrayPointer = (*Native_env)->GetShortArrayElements(Native_env, Buffer, 0);
/*
        for (int i = 0; i < no_of_samples; i++)
        {
            NativeArray[i] = ArrayPointer[i];
        }
*/
         (*Native_env)->ReleaseShortArrayElements( Native_env, Buffer, ArrayPointer, 0 );
    }

int ifReceivedExit = 0;

void Start()
{
    while(ifReceivedExit == 0)
    {
        ReadPCMSamples(NativeArray,160);
    }
}

void Stop()
{
    ifReceivedExit = 1;
}

pthread_t threadID;

void StartThread()
{
int res  = 0;
res = pthread_create(&threadID, NULL, Start, NULL);
if (res != 0)
    __android_log_print(ANDROID_LOG_ERROR, "Thread Creation", "Failed %s", strerror(res));
else
    __android_log_print(ANDROID_LOG_ERROR, "Thread Creation", "Success"); // Thread ID %x res %d", threadID, res);
}

void StopThread()
{
    Stop();
    if(JavaClass)
        (*Native_env)->DeleteGlobalRef(Native_env,JavaClass);

    if(read_fptr)
       fclose(read_fptr);
}

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    return JNI_VERSION_1_6;
}

MainActivity.java

public class MainActivity extends AppCompatActivity 
{
     @Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    InitializeJNI();
}

public void ReadAndroidPCM(int no_of_samples)
{
    int read_samples = audioRecord.read(ReadBuffer, 0, no_of_samples);

    FillNativeBuffer(ReadBuffer,no_of_samples);

    //WriteRecordedDataToFile(ReadBuffer, read_samples, READ);
}

static
{
    System.loadLibrary("VajraAPI");
}

public native void InitializeJNI();
public native void StartNativeThread();
public native void StopNativeThread();
public native void FillNativeBuffer(short[] Buffer, int no_of_samples);
}

MainActivity.c

#include <jni.h>

#include "com_consilient_tech_uday_javaapi_MainActivity.h"
#include "Application.h"

/*
* Class:     com_consilient_tech_uday_javaapi_MainActivity
* Method:    InitializeJNI
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_consilient_tech_uday_javaapi_MainActivity_InitializeJNI
    (JNIEnv *env, jobject obj)
{
Update_NativeJNVVariables(env, obj);
}

/*
* Class:     com_consilient_tech_uday_javaapi_MainActivity
* Method:    StartNativeThread
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_consilient_tech_uday_javaapi_MainActivity_StartNativeThread
    (JNIEnv *env, jobject obj)
{
StartThread();
}

/*
* Class:     com_consilient_tech_uday_javaapi_MainActivity
* Method:    StartNativeThread
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_consilient_tech_uday_javaapi_MainActivity_StopNativeThread
    (JNIEnv *env, jobject obj)
{
StopThread();
}

/*
* Class:     com_consilient_tech_uday_javaapi_MainActivity
* Method:    GetByteArrayFromNative
* Signature: (SI)V
*/
JNIEXPORT void JNICALL Java_com_consilient_tech_uday_javaapi_MainActivity_GetByteArrayFromNative
    (JNIEnv *env, jobject obj, jint no_of_samples)
{
//GetByteArray(no_of_samples);
}

When i try to find the bug, it is crashing at GetShortArrayRegion giving the segmentation fault

Thanks

Uday
  • 1
  • 3
  • Where does `Native_env` come from? You shouldn't be caching `JNIEnv` pointers, in case that's what you're doing. – Michael Mar 15 '16 at 11:43
  • And you should not try to pass `ArrayPointer` to `ReadAndroidPCM`. The Java method has no idea what a `jshort*` is. – Michael Mar 15 '16 at 11:50
  • There is a function Update_JNI_Variables() which is called from Java Layer which updates the Native_env variable and Native_obj with the received env and object variables. – Uday Mar 15 '16 at 11:50
  • _"There is a function Update_JNI_Variables() which is called from Java Layer."_ Uh. Why not call `GetEnv` from the native functions that need to perform JNI calls? (and make sure that you attach the current thread to the VM in case it's detached) – Michael Mar 15 '16 at 11:52
  • That sounds like a strange design. And since you haven't included that code in your question it's impossible to rule out that that isn't part of your problem. So I don't see how the question can be reliably answered right now. – Michael Mar 15 '16 at 11:56
  • Thanks for the response Michael. I am new to this JNI related stuff. I tried to save the ENV variable so that i can access the methods based on that. I am not creating any VM now to attach or detach. I am building the code using AndroidStudio which implements the VM related things (not sure, read somewhere). – Uday Mar 15 '16 at 12:00
  • You don't need to create a VM; it already exists. You do however need to attach the current thread to it if you run native code on a thread separate from the main thread. And you should generally not save `JNIEnv` pointers. – Michael Mar 15 '16 at 12:16
  • Updated the Total Code Michael – Uday Mar 15 '16 at 13:13
  • You're spawning a new thread, and the `JNIEnv` pointer that you saved is not valid in that thread. See e.g. http://stackoverflow.com/questions/30026030/what-is-the-best-way-to-save-jnienv/30026231#30026231 – Michael Mar 15 '16 at 13:38

1 Answers1

0

Thanks Michael for the link provided.

The problem is Caching of the Java Native Environment Variable(as you specified).

Environment shouldn't be cached, whereas the class can be made global by using NewGlobalRef method which will help to use the class in other threads also. The method ID can also me made global.

It is Better to Get the Class and MethodID in the JNI_OnLoad and create Global references for the same.

When we create a new thread, we need to attach the new thread to Java VM. And obtain the JNI Env for further processing.

Uday
  • 1
  • 3