JNI call and callback in Android

This is a cpp file which I used to jni call and callback in one of my project. I post it here so that i do not have to re r&d. It uses android 4.0.3 source to compile.

 

#include "JNIMain.h"
#include "GlobalResource.h"
#include "Recorder.h"

JavaVM *javaVM;
jobject recorderJava;
Recorder recorder;
jmethodID audioCallBackID;
jmethodID videoCallBackID;
extern sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, struct JNICameraContext** context);

extern "C" {

Recorder* recorder;
jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	GlobalResource* gr = GlobalResource::getInstance();
	javaVM = jvm;
	return JNI_VERSION_1_6;
}

jint Java_stopNativeRecording(JNIEnv *env, jobject obj)
{
	if(recorder!=0)
	{
		LOGD("stopping native recorder");
		recorder->stop();
		if(recorderJava!=0)
			env->DeleteGlobalRef(recorderJava);

	}
	else
	{
		LOGD("recording was not started");
	}

	return 0;
}

jint Java_startNativeRecording(JNIEnv *env, jobject obj, jobject jCamera, jobject jSurface)
{
	LOGD("starting native recorder");
	GlobalResource* gr = GlobalResource::getInstance();
	recorderJava = env->NewGlobalRef(obj);


	jclass cls = env->GetObjectClass(obj);
	audioCallBackID = env->GetMethodID(cls,"onAudioData", "([B)V");
	videoCallBackID = env->GetMethodID(cls,"onVideoData", "([B)V");




	sp<Camera> cmr=get_native_camera(env,jCamera, NULL);	
    jclass surfaceClass = env->FindClass("android/view/Surface");
    jfieldID surfaceField = env->GetFieldID(surfaceClass, "mNativeSurface", "I");
    Surface* const  p = (Surface*)(env->GetIntField(jSurface, surfaceField));
	sp<Surface> surface=sp<Surface>(p);

	recorder = new Recorder();
	recorder->start(cmr,surface);

	return 0;
}

jobject Java_getCodecInfo(JNIEnv *env, jobject obj, jobject jCamera, jobject jSurface)
{
	LOGD("starting native recorder");
	GlobalResource* gr = GlobalResource::getInstance();
	recorderJava = env->NewGlobalRef(obj);


	jclass cls = env->GetObjectClass(obj);
	audioCallBackID = env->GetMethodID(cls,"onAudioData", "([B)V");
	videoCallBackID = env->GetMethodID(cls,"onVideoData", "([B)V");




	sp<Camera> cmr=get_native_camera(env,jCamera, NULL);	
    jclass surfaceClass = env->FindClass("android/view/Surface");
    jfieldID surfaceField = env->GetFieldID(surfaceClass, "mNativeSurface", "I");
    Surface* const  p = (Surface*)(env->GetIntField(jSurface, surfaceField));
	sp<Surface> surface=sp<Surface>(p);

	recorder = new Recorder();
	recorder->storeCodecInfo(cmr,surface);



/*******************************************returning**************************************/
	jclass CodecInfoClass = env->FindClass("CodecInfo");
	jmethodID constructor = env->GetMethodID(CodecInfoClass, "<init>", "()V"); // no parameters  
	jobject codecInfo = env->NewObject(CodecInfoClass, constructor); 

	

	//get field ids
	LOGD("here");
	jfieldID spsFieldID = env->GetFieldID(CodecInfoClass, "VIDEO_SPS", "[B");
	jfieldID ppsFieldID = env->GetFieldID(CodecInfoClass, "VIDEO_PPS", "[B");
	jfieldID audioProfileFieldID = env->GetFieldID(CodecInfoClass, "AUDIO_PROFILE", "I");
	jfieldID audioSamplingRateFieldID = env->GetFieldID(CodecInfoClass, "AUDIO_ACTUAL_SAMPLING_RATE", "I");
	jfieldID audioChannelID = env->GetFieldID(CodecInfoClass, "AUDIO_CHANNEL", "I");
	LOGD("here");



	//sps
	
	android::List<AVCParamSet>::iterator itS = gr->mSeqParamSets.begin();

	if(itS != gr->mSeqParamSets.end())
	{
		int seqParamSetLength = itS->mLength;
		jbyteArray vsps = env->NewByteArray(seqParamSetLength);;
		env->SetByteArrayRegion(vsps, 0, seqParamSetLength, (jbyte*)itS->mData); 
		env->SetObjectField (codecInfo, spsFieldID, vsps);
	}
	LOGD("sps done");
	//pps
	android::List<AVCParamSet>::iterator itP = gr->mPicParamSets.begin();

	if(itP != gr->mPicParamSets.end())
	{
		int picParamSetLength = itP->mLength;
		jbyteArray vpps = env->NewByteArray(picParamSetLength);
		env->SetByteArrayRegion(vpps, 0, picParamSetLength, (jbyte*)itP->mData); 
		env->SetObjectField (codecInfo, ppsFieldID, vpps);
	}

	LOGD("pps done");
	//audioProfile
	env->SetIntField(codecInfo, audioProfileFieldID, gr->mProfile);
	//audioSamplingRate
	env->SetIntField(codecInfo, audioSamplingRateFieldID, gr->mActualSampleingRate);
	//audioChannel
	env->SetIntField(codecInfo, audioChannelID, gr->mChannel);

	
	return codecInfo;
}





jint Java_cameraPreviewTest(JNIEnv *env, 
	jobject obj, jobject jSurface)
{

	
	return 0;
}

} //extern c

// these functions are  called from c++
JNIEnv* attachCurrentThreadtoJVM()
{
 	JavaVM *jvm = javaVM;
    JNIEnv *env=NULL;
	jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
	jvm->AttachCurrentThread(&env, NULL);
	return env;
}

void detachCurrentThreadtoJVM()
{
	JavaVM *jvm = javaVM;
	jvm->DetachCurrentThread();
}


void sendDataToJava(JNIEnv* env,Packet* pk, MEDIATYPE dataType)
{
	jbyteArray jbArray = env->NewByteArray(pk->length);
	env->SetByteArrayRegion(jbArray, 0, pk->length, (jbyte*)pk->data);
	if(dataType == AUDIO)
		env->CallVoidMethod(recorderJava, audioCallBackID, jbArray);
	else
		env->CallVoidMethod(recorderJava, videoCallBackID, jbArray);

	delete pk->data;
	delete pk;
	env->DeleteLocalRef(jbArray);
}
JNI call and callback in Android

Android NDK create executable

It is possible to create executable using android ndk. You need adb to push the executable to the device. Modify/create the Android.mk file in jni folder so that ndk-build create executable rather then shared library.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello_world
LOCAL_SRC_FILES := test.cpp

include $(BUILD_EXECUTABLE)

My cpp file contains following code

#include <stdio.h>
int main()
{
	printf("hello world\n");
}

Run ndk-build in jni folder. Then a executable will create in libs/armeabi folder in hello_world name. Attach your device to the pc. Open your console and go the libs/armeabi folder. Run adb push hello_world /data/local/tmp. Now run adb shell. This will open the shell of your device. cd to the /data/local/tmp. Run ./hello_world. The console will show the output. /data is the only executable section in android.

Android NDK create executable

Android NDK startup

This is a simple tutorial or you can say a collection of code for Android NDK beginners. It is possible to compile android native code using android NDK aka Native Development Kit or Android Source. Using android source is better because we can use more features that comes with android but not available in NDK. Now I will try to show both way. I prefer to use terminal/console for compiling the code and using a text editor (like sublime text) and a unix environment (linux/os x). Though one can use Eclipse as IDE for NDK. The first method here is using Eclipse IDE and NDK. One can easily  google for how to add NDK plugin in eclipse.

Method 1. Using Eclipse

Download ndk, sdk, eclipse. Add android sdk/ndk plugin to eclipse.

Create a new Android project. Right Click on the project in project explorer->Android tools->add native support. Then you will find a jni folder in your project. This folder contains a cpp file and Andriod.mk file (make file).
Edit the cpp file as follows

#include
extern "C" {
int Java_com_example_ndkexample_MainActivity_add(JNIEnv *env, jobject thiz, int a, int b)
{
	return (a+b);
}
}

JNI for android is much simpler then JNI in java. This method takes two int as input and returns their sum. Edit Android.mk, add the cpp file as local_src_files and add a name for your library, this name will be used to load the library. Here is my sample

LOCAL_MODULE    := test
LOCAL_SRC_FILES := test.cpp

Right click on the project, build project. In your java MainActivity load the library statically. Add the native function.

public class MainActivity extends Activity {

	public native int add(int a, int b);
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		int r=add(1,2);
		Log.d("ndkexample", "The sum of 1 and 2 is "+r);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

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

}

If you run the project, you will see the result in log.

2. Using command line
The initial steps are similar, create a new project in eclipse and create a jni folder there. Add source file and Andriod.mk file.
Now add the ndk folder in your path variable. Open terminal and CD to the project/jni folder.
Run command: ndk-build. It will compile the source code, create the library file and save it to libs/armeabi folder (same what as ide, just more manual). Run the project as previous.

Android NDK startup